fix(release): stop hiding sandbox build failures and check the prepared package early - #10784
Conversation
…uild failures
The docker sandbox build in `integration_docker` has been failing every
release since 0.22.3-release-7df5ac68. The build itself is fine; what fails is
`npm run prepare:package` inside the Dockerfile's builder stage:
Error: Prepared package unpacked size 101897869 bytes exceeds 100663296 bytes
`assertPreparedPackageSize`'s 96 MiB budget was set in #6691 when the prepared
package was ~80.6 MiB. It has grown with ordinary feature work since —
96,802,417 bytes at 0.22.1, 98,582,313 at the 08-31 nightly, 101,897,869 now,
with the OpenTUI migration batches carrying two UI stacks at once — and it
crossed the ceiling. Raise the budget to 112 MiB, the same "headroom, not
target" call #6688 and #6691 made before it. vendor/ripgrep (24 MiB, five
platform binaries) and web-shell/assets (18 MiB, mostly shiki grammars,
mermaid and cytoscape) are the two places worth trimming if we would rather
bring the package down than keep raising the ceiling; that is a separate
change.
The reason a one-line size error cost a 20-minute release run to diagnose is
the other half of this commit:
- `build_sandbox.js` sent the image build's output to /dev/null unless VERBOSE
was set, so the failure surfaced as an execSync stack trace with
`stdout: null` and no way to tell what broke without rebuilding by hand.
`e2e.yml` had already worked around this by setting VERBOSE; the release
workflow had not. Now CI streams the output, and a quiet local build keeps
it and prints the tail when the build fails.
- Nothing ran `prepare:package` before the publish job, so its guards were
first exercised by the Dockerfile — the slowest consumer in the pipeline.
`quality_build` now runs bundle + prepare:package after uploading its build
artifact, which reports the same failure in minutes without touching what
downstream jobs consume.
|
Thanks for the PR! Template looks good ✓ — all sections filled in, including before/after logs and the Chinese translation. Problem: observed, not theoretical. Every release since Direction: aligned — a blocked release pipeline is a concrete problem, and raising the budget follows the same call #6688 (80 → 85 MiB) and #6691 (85 → 96 MiB) made when ordinary growth reached the ceiling. One note: this lands in release infrastructure and the sandbox build script, which the gate treats as a maintainer sign-off area — so I'll review it fully but hand the final call to a maintainer rather than approving it through automatically. Size: no core package paths touched. 65 production lines (release workflow + Approach: the scope feels right. Three changes, each pulling its weight: the budget raise unblocks releases, the output-capture fix turns the next failure from a twenty-minute mystery into a one-line error, and the early Risk: no elevated risk signals — no high-risk paths matched. Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ —— 各部分均已填写,包括前后对比日志和中文翻译。 问题:已观测到的实际问题,不是理论假设。自 方向:对齐 —— 发布流水线被阻塞是具体的问题;提高预算与 #6688(80 → 85 MiB)、#6691(85 → 96 MiB)在正常增长触顶时的做法一致。一点说明:本 PR 落在发布基础设施和 sandbox 构建脚本上,属于 gate 规则中需要维护者拍板的领域——因此会完整评审,但最终决定交给维护者,不会自动批准。 规模:未触及核心包路径。生产代码 65 行(发布工作流 + 方案:范围合理。三处改动各司其职:提高预算解除发布阻塞;输出捕获让下一次失败从二十分钟的谜团变成一行错误; 风险:无升级风险信号——未命中高风险路径。 进入代码审查 🔍 — Qwen Code · qwen3.8-max Reviewed at |
Code reviewI wrote my baseline proposal before reading the diff: raise the budget constant with headroom (precedent of #6688/#6691), capture the docker build output and print it on failure instead of discarding it, and add an early
No correctness, security, or convention issues found. Nothing blocking. Test evidenceFrom the PR's own CI at the reviewed commit — no failures so far, with the main suite still running; 31 skipped checks are conditional bot lanes. The author's local results (35 script tests + 64 workflow/script tests passing, five environment-dependent failures identical on the parent commit) are their claim, not independently re-run here. Final CI results for
One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。 The pending Not verified: a real container image build on the raised budget — the author had no container runtime locally, and PR CI has no docker image build leg (that is release-only). Sandboxed verification would settle the load-bearing half of the claim: 中文说明代码审查:读 diff 前先独立写了方案(按先例提高预算常数并留余量、失败时捕获并打印 docker 构建输出、在发布工作流提前加 测试证据:来自被审提交上 PR 自身的 CI——目前无失败,主套件仍在运行(新脚本测试跑在 — Qwen Code · qwen3.8-max Reviewed at |
|
Confidence: 3/5 — clean review of a real, well-evidenced problem; the cap is policy, not doubt. This changes the release pipeline and the sandbox build script, an area the gate hands to a maintainer, and CI had not fully settled at review time. Stepping back: the problem is unambiguous — releases have been failing on a known byte count for days, with two open release-failure issues as evidence. The implementation is what I would have proposed: the budget raise follows the #6688/#6691 precedent, the output fix turns the next such failure from an opaque stack trace into a one-line error, and the early gate in Two gaps remain, and neither is something I can close from the diff: the main Verdict: not approving — nothing is wrong in the diff, but a release-infrastructure change is exactly the class of PR where the gate's job ends at "reviewed, looks right" and a maintainer makes the call. 中文说明回顾整体:问题毫无疑问——发布已连续多天因一个已知的字节数失败,且有两个未关闭的发布失败 issue 为证。实现与我独立提出的方案一致:预算提升沿用 #6688/#6691 的先例;输出修复让下一次同类失败从看不懂的堆栈变成一行错误; 仍有两个我无法仅凭 diff 关闭的缺口:审查时主 结论:不批准——diff 本身没有问题,但发布基础设施改动正是 gate 的职责止步于"已审查、看起来正确"、由维护者拍板的那一类。置信度 3/5 是政策上限,不是真实疑虑。 — Qwen Code · qwen3.8-max Reviewed at |
|
⏸️ Deferring to maintainer — the review is clean and the problem is real, but this PR changes the release pipeline and the sandbox build script, which the triage gate does not auto-approve. Two items for the human call: (1) the main 中文说明⏸️ 转交维护者——评审干净、问题真实,但本 PR 改动了发布流水线与 sandbox 构建脚本,triage gate 不会自动批准此类改动。需要人工判断的两点:(1)审查时主 — Qwen Code · qwen3.8-max |
#10781 raised the budget to 112 MiB on main while this branch was open, with the same constant and the same test values this branch carried. Take main's version of prepare-package.js wholesale — the budget half of this branch is now upstream, and what remains is the diagnosability half: build_sandbox.js surfacing the container build's output, and quality_build running prepare:package before the Dockerfile does.
|
Qwen Code review did not complete successfully. The review pipeline failed before a review could be posted. A transient error is retried automatically; if you are seeing this, retry with |
Code Coverage Summary
CLI Package - Full Text ReportCore Package - Full Text ReportFor detailed HTML reports, please see the 'coverage-reports-22.x-ubuntu-latest' artifact from the main CI run. |
E2E review report — head
|
| Run | Result |
|---|---|
fake docker fails build, quiet (VERBOSE='', CI='') |
exit ≠ 0; the fake's stdout marker and its stderr line both reach the operator, inside the labeled 200-line frame |
fake docker fails build, CI='true' |
exit ≠ 0; marker streamed; the frame is not reprinted (end of build output absent) |
| fake docker succeeds, quiet | rc=0, built ghcr.io/qwenlm/qwen-code:0.22.3, child output captured and not echoed |
fake docker succeeds, CI='true' and VERBOSE='true' |
rc=0, child output streamed (Successfully tagged final-image:probe visible) |
The two success rows are what the shipped test does not cover, and they are the ones that matter for a mutable release script: capturing instead of discarding changes neither the exit status nor the log shape of a healthy build.
Both halves of the fix are pinned by the new test
scripts/tests/build-sandbox-output.test.js at head: 2/2 pass. Two mutations, each reverting one line to its pre-PR form, each turning the suite red, with the file restored sha256-identical after both:
- quiet branch of
buildStdioback to'ignore'→ red (the capture is what the first test asserts on); if (!streamBuildOutput) { printCapturedBuildOutput(error); }deleted → red (the tail print is what surfaces it).
Static checks around the change
- No dangling reference to the old
buildStdoutanywhere in the file;sandboxCommandis module-scoped (:64) and therefore in scope for the frame header at:136; the newcatchsits before the pre-existingfinallythat removes the temporary podman auth file and rethrows, so the podman/Windows cleanup path is unchanged. The post-successCI artifact file … already existsthrow also lands in the new catch, but with no stdout/stderr the helper returns early and prints no misleading frame. - The CI-streaming premise checks out against the workflows: only
e2e.ymlsetsVERBOSE: 'true'(three steps);release.yml'snpm run build:sandbox -- -s --no-prune -i "$sandbox_image"(:732) does not, and Actions always setsCI— so that lane goes from discarded output to streamed, which is exactly the asymmetry the description describes. Verify Prepared Packageis the last step ofquality_build, afterUpload Build Outputs, and release jobs do not share a workspace:Restore workspace ownershipwipes$RUNNER_WORKSPACEat the start of every job on the self-hosted pool (behind its symlink /../ denylist guards), so the step'sdist/rewrite and packaging side effects cannot reachquality_typecheck,integration_dockerorpublish.prepare:package's guards are satisfiable at that point in the job:npm run build(all workspaces) has already run, and the step's ownnpm run bundlere-runs generate + esbuild +copy_bundle_assets.jswithoutDEV=true(the earliercheck:serve-fast-path-bundlebundled withDEV=true), producing every pathverifyBundleArtifactsrequires —dist/cli.js,dist/vendor,dist/bundled/qc-helper/docs,dist/web-shell/index.html,dist/web-shell/assets. The budget it enforces is main's 112 MiB against the ~101.9 MB the description measures.- The claim about where the guard used to fire first is accurate:
Dockerfile:33runsnpm run prepare:packagein the builder stage.
Non-blocking notes
integration_dockerdoes not wait for this gate. It declaresneeds: 'prepare'only (as doesintegration_none), so it runs in parallel withquality_build. The new step makes the cause legible in minutes but does not stop the ~20-minute image build from also failing. The stage-2 comment's "the downstream jobs alreadyneeds: quality_build" holds forquality_typecheck/workspace_tests/quality_scripts, not for the job that actually builds the image. Adding that dependency is a pipeline-policy call rather than a defect — the description only claims faster diagnosis, and it delivers that.- The 128 MiB
maxBuffertrades one failure mode for another: a build that would have succeeded but emits more than that gets its child killed byexecSync. The comment states the trade-off; I did not exercise it (it would need a >128 MiB fake build log). e2e.yml's threeVERBOSE: 'true'settings are now redundant with theCIdefault. Harmless, and droppable in a follow-up.
About my own environment, so the numbers are not misread
Running the whole scripts/tests suite in my scratch checkout gives 2061 passed / 8 failed, and all 8 are artifacts of how I ran it rather than of this PR: 4 in cross-package-contracts.test.js shell out to git grep --untracked and my scratch tree is a git archive extraction, not a git checkout; 1 in install-script.test.js dies on ENOENT … packages/audio-capture/dist (a symlinked dist in my tree); 3 in qwen-autofix-workflow.test.js / qwen-pr-review-workflow.test.js assert chmod/sudo write-protection semantics that do not hold when the process is root (env forge landed: backing file writable instead of env forge blocked: backing file locked). None of those files is touched here — quality_scripts in CI is the authority for them.
复查结论(head 9a603a5b37,未发现阻塞问题):我在 head 的临时检出里真实执行了 node scripts/build_sandbox.js -s --no-prune(PATH 前置假 docker,不触碰容器运行时),覆盖失败与成功两条路径、安静/CI/VERBOSE 三种模式:失败时安静路径把子进程 stdout+stderr 打进带标记的 200 行框、CI 路径直接流式且不重复打印;成功时安静路径 rc=0 且输出被捕获不回显、CI/VERBOSE 路径正常流式——成功路径是新测试没覆盖的部分,证明"改为捕获"不会改变健康构建的退出码与日志形态。新测试 head 下 2/2 通过;两处变异(buildStdio 安静分支改回 'ignore'、删掉失败时打印 tail 的 catch 分支)分别让套件变红,之后文件 sha256 均复原一致,说明修复的两半都被钉住。静态核对:旧变量 buildStdout 无残留引用;sandboxCommand 为模块级、在 :136 的框标题里可用;新 catch 位于清理 podman 临时 auth 文件的既有 finally 之前并原样 rethrow;成功后 "artifact 文件已存在" 的抛出也会进入该 catch,但因无 stdout/stderr 而不会打印误导性框。工作流侧:只有 e2e.yml 设了 VERBOSE,release.yml:732 的 build:sandbox 没设,而 Actions 必然带 CI,所以该车道由"丢弃输出"变为"流式输出"。Verify Prepared Package 位于 Upload Build Outputs 之后、是 quality_build 最后一步,且发布作业之间不共享工作区(每个作业开头 Restore workspace ownership 会带符号链接/../denylist 守卫清空 $RUNNER_WORKSPACE),副作用不会传到下游;该步的 npm run bundle 不带 DEV=true,能产出 verifyBundleArtifacts 要求的全部路径,预算为 main 的 112 MiB。Dockerfile:33 确实运行 npm run prepare:package,描述中"守卫最早在镜像构建里触发"的说法成立。非阻塞提示:integration_docker 只声明 needs: 'prepare',与 quality_build 并行,因此新步骤能几分钟内说明原因,但不能阻止那次约 20 分钟的镜像构建同样失败(stage-2 评论中"下游作业已 needs: quality_build"对 typecheck/workspace_tests/quality_scripts 成立,对真正建镜像的作业不成立);128 MiB maxBuffer 是明确的取舍,未实测;e2e.yml 里三处 VERBOSE 现已冗余。另需说明:我在临时检出里跑完整 scripts/tests 得到 2061 通过 / 8 失败,8 项全部源于我的运行环境(git grep 需要 git 检出、dist 为符号链接、以 root 运行时 chmod/sudo 写保护语义不成立),与本 PR 无关,以 CI 的 quality_scripts 为准。未 approve:该 head 尚无 gate review,且新脚本测试所在的 Test 车道发帖时仍在运行。
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
Reviewed. Suggestions are inline.
Not explored to full depth (tool budget reached): "agent 3b": none — but note I could not execute the new test or measure a real docker build's output volume: this review worktree has no node_modules ( ls node_modules →…; "agent 2": none — but note the one check I could not run end-to-end: executing scripts/build_sandbox.js and the new vitest file in this worktree, because node_modules ….
Not linted (tool limitation, not a blocker): the executable-script lint — .github/workflows/release.yml: actionlint embedded-shell source mapping is not yet supported — not linted.
中文说明
已审查。 建议见行内评论。
未探索到全部深度(达到工具调用预算):"agent 3b":none — but note I could not execute the new test or measure a real docker build's output volume: this review worktree has no node_modules ( ls node_modules →…;"agent 2":none — but note the one check I could not run end-to-end: executing scripts/build_sandbox.js and the new vitest file in this worktree, because node_modules …。
未检查(工具限制,非阻断):the executable-script lint — .github/workflows/release.yml: actionlint embedded-shell source mapping is not yet supported — not linted。
— qwen3.8-max-2026-09-02 via Qwen Code /review (v0.22.3)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
Released in v0.23.0. |
What this PR does
Two changes to how the release pipeline reports packaging failures, both of them lessons from the release that #10781 just unblocked:
build_sandbox.jsno longer discards the container build's output. CI streams it; a quiet local build keeps it and prints the tail when the build fails.quality_buildrunsnpm run bundle && npm run prepare:packageafter uploading its build artifact, soprepare:package's guards are exercised by a fast job instead of first firing inside the Dockerfile.This branch originally also raised the unpacked size budget to 112 MiB. #10781 landed that on main first, with the same constant and the same test values, so main is merged in here and its version of
scripts/prepare-package.jstaken wholesale. What is left is only the part about how long the failure took to identify.Why it's needed
The 96 MiB budget tripping was a one-line error. Reading that line cost a twenty-minute release run, twice, because of two things this PR fixes.
build_sandbox.jssent the image build's output to/dev/nullunlessVERBOSEwas set, so the failure surfaced as anexecSyncstack trace withstdout: null— no indication of which Dockerfile line failed, and no way to find out except rebuilding by hand.e2e.ymlhad already worked around this by settingVERBOSE: 'true'on its build step;release.ymlhad not. That is why the actual message (Prepared package unpacked size 101897869 bytes exceeds 100663296 bytes) appears in the E2E logs and is absent from the release logs for the same commit — the release run reported only thatdocker buildexited 1. Keying the default onCIrather than on each workflow remembering to setVERBOSEmeans the next failure inside that image is legible wherever it happens, including on a developer's machine.The ordering is the other half. Nothing ran
prepare:packagebetweenBuild Bundleand the publish job, so its guards — the size budget, the forbidden-literal scan, the missing-bundle-artifact checks — were first exercised by the slowest consumer in the pipeline, and a packaging problem could only be discovered by building a container image.quality_buildalready installs and builds; adding bundle + prepare there reports the same failure in minutes. It runs afterUpload Build Outputson purpose, so the artifact downstream jobs consume stays byte-for-byte what the earlier steps produced.The size budget will be reached again — the package has grown from 96,802,417 bytes at 0.22.1 to 101,897,869 now, and the OpenTUI migration carries the ink and OpenTUI stacks in the same bundle until it finishes. When it does, this is the difference between a five-minute red job that names the number and a twenty-minute one that prints nothing. (If we would rather bring the package down than keep raising the ceiling, the two places worth trimming are
vendor/ripgrep, 24 MiB across five platform binaries, andweb-shell/assets, 18 MiB of mostly shiki grammars, mermaid and cytoscape. Separate change, and a product decision.)Reviewer Test Plan
How to verify
npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/build-sandbox-output.test.js— new; puts a fakedockeronPATHthat fails thebuildsubcommand, and asserts the output reaches the operator on both paths: captured and printed on failure in the quiet path, streamed underCI. Under a second, no container runtime needed.BUILD_SANDBOX_FLAGS='--target nosuchstage' QWEN_SANDBOX=docker npm run build:sandbox -- -s --no-prune— docker's own error text is now in front of you instead ofstdout: null.npm run bundle && npm run prepare:package— what the newquality_buildstep runs. Completes on main today; one28f17de7a~1it reproduces the release failure in about two minutes rather than twenty.Evidence (Before & After)
Before — the entire failure as the release log recorded it (run 33594509319,
integration_docker):After — same failure, quiet (non-CI) path:
Under CI the output streams as the build runs, so a hung or slow layer is visible while it happens rather than only after the failure.
Tested on
Environment (optional)
Linux, no container runtime available. After merging main:
package-assets.test.js,build-sandbox-output.test.js,release-workflow.test.jsandpackage-scripts.test.js— 99 passed, 2 skipped. Prettier, eslint and yamllint clean on every touched file. The fulltest:scriptssuite is 70 passed / 5 failed here, and the same five files fail identically on the parent commit (install-script,qwen-pr-review-workflow,serve-ab-workflow,qwen-autofix-workflow,unit-vitest-configs) — they need a real build output, network access, a non-root user, or workspacenode_modulesthis environment does not have. The container build itself is left to CI.Risk & Scope
maxBufferis raised to 128 MiB — well clear of a real build's output, since exceeding it would kill an otherwise healthy build. CI takes the streaming path and buffers nothing. The newquality_buildstep adds a bundle to a job that already installs and builds.Linked Issues
Context, not fixes: #10779 and #10775 (the release failures this came out of) were caused by the size budget, which #10781 raised.
中文说明
此 PR 的作用
两项改动,针对发布流水线如何报告打包失败,都来自 #10781 刚刚解封的那次发布:
build_sandbox.js不再丢弃容器构建的输出。CI 下直接 stream;本地安静模式会保留输出,并在构建失败时打印末尾部分。quality_build在上传构建产物之后执行npm run bundle && npm run prepare:package,让prepare:package的各项校验由一个快速 job 触发,而不是第一次在 Dockerfile 里被触发。本分支原本还包含把体积上限提到 112 MiB 的改动。#10781 先一步把它合进了 main,常量与测试数值完全相同,因此这里已合入 main 并整份采用其
scripts/prepare-package.js。剩下的只有「这个失败花了多久才被定位」这一部分。为什么需要
96 MiB 上限被触发本身只是一行报错。但读到这一行的代价是两次二十分钟的发布运行,原因就是本 PR 修复的这两点。
除非设置
VERBOSE,否则build_sandbox.js会把镜像构建的输出送进/dev/null,失败时只剩一个stdout: null的execSync堆栈 —— 看不出 Dockerfile 哪一行失败,除了手动重跑一遍别无他法。e2e.yml早已通过在构建步骤上设置VERBOSE: 'true'绕开这一点,release.yml没有。这就是为什么同一个 commit 的真实报错(Prepared package unpacked size 101897869 bytes exceeds 100663296 bytes)出现在 E2E 日志里,却在发布日志里完全缺席 —— 发布运行只报告了docker build退出码为 1。把默认行为绑定在CI上,而不是指望每个 workflow 都记得设置VERBOSE,意味着下一次镜像内部的失败无论发生在哪里都可读,包括开发者本机。顺序问题是另一半。
Build Bundle与 publish job 之间没有任何地方跑prepare:package,于是它的各项校验 —— 体积闸门、禁用字符串扫描、缺失产物检查 —— 第一次被触发是在整条流水线最慢的消费者里,打包问题只能靠构建一个容器镜像才能发现。quality_build本来就要安装依赖并构建,在那里加上 bundle + prepare,几分钟内就能报出同样的失败。它特意放在Upload Build Outputs之后,以保证下游 job 拿到的 artifact 与前面步骤产出的完全一致。体积上限还会再次被触碰 —— 这个包已经从 0.22.1 的 96,802,417 字节长到现在的 101,897,869,而在 OpenTUI 迁移完成前,ink 与 OpenTUI 两套栈会同时留在同一个 bundle 里。到那时,本 PR 的差别就是:一个五分钟、直接给出数字的红色 job,还是一个二十分钟、什么都不打印的。(如果我们更希望把包瘦下来而不是继续抬高上限,值得下手的两处是
vendor/ripgrep(五个平台二进制共 24 MiB)和web-shell/assets(18 MiB,主要是 shiki 语法、mermaid 和 cytoscape)。那是单独的改动,也是产品决策。)审阅者测试计划
如何验证
npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/build-sandbox-output.test.js—— 新增;在PATH上放一个让build子命令失败的假docker,断言两条路径上输出都会送到使用者面前:安静模式下捕获并在失败时打印,CI下直接 stream。1 秒内完成,不需要容器运行时。BUILD_SANDBOX_FLAGS='--target nosuchstage' QWEN_SANDBOX=docker npm run build:sandbox -- -s --no-prune—— 现在你看到的是 docker 自己的错误文本,而不是stdout: null。npm run bundle && npm run prepare:package—— 即新增的quality_build步骤所跑的内容。在今天的 main 上正常结束;在e28f17de7a~1上则用约两分钟(而非二十分钟)复现那次发布失败。证据(前后对比)
修复前 —— 发布日志记录到的全部内容(run 33594509319,
integration_docker):修复后 —— 同一个失败,安静(非 CI)路径:
CI 下输出随构建实时 stream,因此某一层卡住或变慢时能当场看到,而不是只能等失败之后。
测试平台
环境(可选)
Linux,无可用容器运行时。合入 main 之后:
package-assets.test.js、build-sandbox-output.test.js、release-workflow.test.js、package-scripts.test.js—— 99 passed,2 skipped。所有改动文件的 prettier、eslint、yamllint 均干净。完整的test:scripts套件在此环境下为 70 passed / 5 failed,且同样的五个文件在父提交上以完全相同的方式失败(install-script、qwen-pr-review-workflow、serve-ab-workflow、qwen-autofix-workflow、unit-vitest-configs)—— 它们需要真实构建产物、网络访问、非 root 用户,或此环境不具备的 workspacenode_modules。容器构建本身交给 CI 验证。风险与范围
maxBuffer提高到 128 MiB —— 远高于真实构建的输出量,因为一旦超限会杀掉一个本来健康的构建。CI 走 stream 路径,不做任何缓冲。新增的quality_build步骤给一个本就要安装并构建的 job 增加了一次 bundle。关联 Issue
仅作背景,非修复:#10779 与 #10775(本 PR 起因的那两次发布失败)由体积上限导致,该上限已由 #10781 提高。