fix(standalone): Route serve shim through cli-entry - #5977
Conversation
|
Thanks for the PR! Template looks good ✓ — all required sections present, bilingual, risk/scope well articulated. On direction: this is a narrowly-scoped packaging fix — extending the existing On approach: 2 files, +157/-5 — the diff is tight and every line serves the stated goal. Shell-level Moving on to code review. 🔍 中文说明感谢贡献! 模板完整 ✓ — 所有必需章节齐全,双语,风险/范围描述清晰。 方向:范围很窄的打包修复——把已有的 方案:2 个文件,+157/-5——diff 紧凑,每行都服务于目标。生成 shim 中 shell 层级的 进入代码审查 🔍 — Qwen Code · qwen3.7-max |
Code ReviewIndependent proposal before reading the diff: modify Reviewed the diff carefully:
No critical blockers, no AGENTS.md violations. Clean, minimal diff. TestingRan the full All 4 PR-specific tests pass. The single failure is a pre-existing issue — Note on tmux testing: this PR modifies build/packaging scripts (shim generators and Maintainer @wenshao has independently verified this PR with a real end-to-end standalone build on macOS, live process-tree inspection confirming 中文说明代码审查看 diff 之前的独立方案:修改 逐项审查:
无关键阻塞项,无 AGENTS.md 违规。Diff 干净、最小化。 测试在 Linux(Node 22.x)上对 PR head tmux 测试说明: 本 PR 修改的是构建/打包脚本(shim 生成器和 维护者 @wenshao 已独立验证此 PR:macOS 上的真实端到端 standalone 构建、进程树检查确认 — Qwen Code · qwen3.7-max |
|
This is a focused, well-scoped packaging fix. The motivation is clear — standalone users were missing the My independent proposal before reading the diff was identical — All 4 new tests pass on Linux. The one pre-existing failure is unrelated. Maintainer @wenshao's independent verification — real end-to-end build, live process-tree confirmation, and mutation testing — provides strong evidence that this works correctly and the regression tests actually guard the change. LGTM, approving. ✅ 中文说明这是一个聚焦、范围合理的打包修复。动机清晰——standalone 用户缺少 npm 安装已有的 看 diff 之前的独立方案与提交完全一致——shim 生成器中的 Linux 上 4 个新增测试全部通过。1 个预先存在的失败与 PR 无关。维护者 @wenshao 的独立验证——真实端到端构建、进程树确认、变异测试——提供了强有力的证据。 LGTM,批准。✅ — Qwen Code · qwen3.7-max |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
f3a9b15 to
c3a6b7b
Compare
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
No review findings. Downgraded from Approve to Comment: CI still running.
— qwen3.7-max via Qwen Code /review
wenshao
left a comment
There was a problem hiding this comment.
No issues found. LGTM! ✅
— qwen3.7-max via Qwen Code /review
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
No review findings. Downgraded from Approve to Comment: CI still running.
— qwen3.7-max via Qwen Code /review
wenshao
left a comment
There was a problem hiding this comment.
The PR description reports 2 files / +154/-5, but the actual branch diff against main is 22 files / +5261/-51 — the branch bundles previously-reviewed features (loop task file + tick resolver from #5884/#5890, autofix workflow tiering, relative postinstall patch dir from #5973) on top of the titular serve shim. Triage stages 1-3 evaluated only the described scope; reviewers skimming the description will not expect ~5k lines of loop / cronScheduler / workflow changes when they open the diff. Consider splitting the bundled features into separate PRs so each lands with an accurate description and a focused review.
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
No critical issues found. Downgraded from Approve to Comment: CI still running.
— qwen3.7-max via Qwen Code /review
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
✅ Maintainer local verification — real build + functional + mutation testingVerified PR head Environment: macOS (darwin-arm64), Node v22.22.2, fresh 1. Real end-to-end standalone build (darwin-arm64)Ran the documented flow that this PR targets —
2. Functional run of the built archive (the key evidence)Launched the real packaged binary This matches the design exactly:
Daemon was fully functional: Non-serve path still uses the original launcher (verified live): 3. Regression testsFull
4. Mutation testing (proves the new tests actually catch regressions)Reverted each source change individually and confirmed the matching test flips to red:
The third mutation also confirms the exact bug this PR fixes: without the skip, the real Notes / observations (non-blocking)
🇨🇳 中文版本(完整对应)✅ 维护者本地验证 —— 真实构建 + 功能运行 + 变异测试在干净 worktree 上验证 PR head 环境: macOS(darwin-arm64),Node v22.22.2,全新 1. 真实端到端 standalone 构建(darwin-arm64)按本 PR 针对的文档流程跑了一遍 ——
2. 对构建产物的真实功能运行(关键证据)启动真实打包出来的二进制 完全符合设计:
Daemon 完全可用: 非 serve 路径仍走原启动器(已实测): 3. 回归测试在 PR head 上跑完整的
4. 变异测试(证明新增测试确实能抓回归)逐一回退每处源码改动,确认对应测试翻红:
第三个变异同时印证了本 PR 修复的确切 bug:缺少该跳过逻辑时,真实的 备注 / 观察(不阻塞合并)
|
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
No review findings. Downgraded from Approve to Comment: CI still running.
— qwen3.7-max via Qwen Code /review
|
@qwen-code /triage |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship. ✅
✅ Local real-build verification (maintainer reference)Verified PR head 1. Packaging test suite (
|
| Check | Result |
|---|---|
lib/cli-entry.js shipped |
✅ present |
lib/postinstall.js (npm-only) |
✅ skipped (absent) |
lib/patches/ (npm-only) |
✅ skipped (absent) |
node/bin/node |
✅ present & executable |
3. Runtime routing — generated bin/qwen executed (the decisive test)
Discriminator: serve routes through cli-entry.js (in-process import, compile cache, no --expose-gc → exposeGc=false); everything else runs --expose-gc cli.js directly (exposeGc=true).
| Invocation | Route | Observed |
|---|---|---|
qwen serve |
cli-entry fast path | exposeGc=false ✅ |
qwen serve --port 9 |
cli-entry fast path | exposeGc=false ✅ |
qwen --version |
--expose-gc cli.js |
exposeGc=true ✅ |
qwen (no args) |
--expose-gc cli.js |
exposeGc=true ✅ |
qwen --version serve |
fallback (flag before subcmd) | exposeGc=true ✅ (documented limitation) |
cli-entry.js <non-serve> |
spawns --expose-gc child |
child ppid = cli-entry pid ✅ |
The serve path was re-run 9× (copied node, via shim, and system node) — 9/9 stable. (One initial run hit a Killed: 9; reproduced as a one-off macOS SIGKILL on a freshly-copied hardened-runtime node binary, unrelated to the PR — it never recurred.)
4. Mutation testing (do the tests actually guard the behavior?)
Reverting each PR behavior makes its test fail — confirming the tests are meaningful:
- Remove shim serve-routing → both shim tests fail (
expected … to contain 'if "%~1"=="serve" goto serve'/'if [ "${1:-}" = "serve" ]'). - Drop
cli-entry.jsfromDIST_REQUIRED_PATHS→requires the standalone cli-entry wrapperfails (expected [Function] to throw). - Drop the npm-only skip →
skips npm-only artifactsfails (Unexpected dist asset: …/dist/patches).
Observations (non-blocking)
servenow runs without--expose-gc— intentional, and it makes standaloneservematch the npm-installedservepath (whosebinis alreadycli-entry.js). The onlyglobal.gc()consumer ismemoryPressureMonitor'strigger_gc, which is guarded (typeof global.gc === 'function') and degrades to a warning; channel subprocesses still get--expose-gcexplicitly via the ACP bridge (spawnChannel.ts). So the standalone daemon's main process loses the explicittrigger_gcGC — worth a conscious ack, but consistent with npm behavior and gracefully handled.- Maintenance coupling:
DIST_NPM_PACKAGE_ONLY_ENTRIES(postinstall.js,patches) must stay in sync with whateverprepare-package.jsemits as npm-only. If a future npm-only artifact is added without updating this set, packaging fails loudly (Unexpected dist asset) rather than mis-shipping — safe default. - Windows shim verified by content assertion + logic review (the
exit /b %ERRORLEVEL%after the non-serve line correctly prevents fall-through into:serve); not executed (no Windows host), consistent with the PR's own status table.
Verdict
LGTM — recommend merge. Routing is correct and stable at runtime, content/skip/required-asset behaviors all hold, and the new tests genuinely guard them. The lone non-passing test is an environmental build-artifact gap, not a code issue.
中文版(完整对应)
✅ 本地真实构建验证(维护者合并参考)
在隔离 worktree 中检出 PR head ecba1e90b,环境 macOS darwin-arm64 / Node v22.22.2,全程通过真实 tmux 会话执行。除单元测试外,我还真实构建了一个 standalone archive(真实 Node runtime + prepare:package 实际生成的 cli-entry.js wrapper),并实际执行生成的 shim,在运行时观测路由行为。
1. 打包测试套件(scripts/tests/install-script.test.js,PR head)
105 通过 · 9 跳过(平台门控)· 1 失败
4 个新增/修改的测试全部通过:
- ✓
requires the standalone cli-entry wrapper in dist - ✓
packages a win-x64 standalone archive(断言新增的goto serve与两个exit /b) - ✓
skips npm-only artifacts staged in dist - ✓
packages a Unix standalone archive with a serve fast path shim
唯一失败项 does not package audio-capture test artifacts 是环境问题,非回归:报错 ENOENT … packages/audio-capture/dist,因为我的全新 worktree 从未构建该 native addon(该测试伪造一个 prebuild,从而让 copyNativeAddon 越过提前返回、进入 cpSync(audio-capture/dist))。PR 完全没有改动 audio-capture。佐证: 补上一个最小的 audio-capture/dist 后,同一测试即通过。CI 会先构建 addon,所以在 CI 是绿的。
2. 真实 archive 构建 + 内容断言
node scripts/create-standalone-package.js --target darwin-arm64 --node-archive <真实 node> … → Created qwen-code-darwin-arm64.tar.gz。解包后:
| 检查 | 结果 |
|---|---|
lib/cli-entry.js 已打包 |
✅ 存在 |
lib/postinstall.js(npm-only) |
✅ 已跳过(不存在) |
lib/patches/(npm-only) |
✅ 已跳过(不存在) |
node/bin/node |
✅ 存在且可执行 |
3. 运行时路由 —— 实际执行生成的 bin/qwen(决定性验证)
判别依据:serve 走 cli-entry.js(进程内 import、compile cache、不带 --expose-gc → exposeGc=false);其余命令直接走 --expose-gc cli.js(exposeGc=true)。
| 调用 | 路由 | 观测 |
|---|---|---|
qwen serve |
cli-entry 快路径 | exposeGc=false ✅ |
qwen serve --port 9 |
cli-entry 快路径 | exposeGc=false ✅ |
qwen --version |
--expose-gc cli.js |
exposeGc=true ✅ |
qwen(无参数) |
--expose-gc cli.js |
exposeGc=true ✅ |
qwen --version serve |
回退(子命令前带 flag) | exposeGc=true ✅(已记录的限制) |
cli-entry.js <非 serve> |
spawn 带 --expose-gc 子进程 |
子进程 ppid = cli-entry pid ✅ |
serve 路径重跑 9 次(复制版 node、经 shim、系统 node)—— 9/9 稳定。(首次出现过一次 Killed: 9,复现确认为 macOS 对刚复制的 hardened-runtime node 二进制的一次性 SIGKILL,与 PR 无关,未再复现。)
4. 变异测试(测试是否真正守住行为?)
逐一回退 PR 的每项行为,对应测试都失败 —— 证明测试有效:
- 移除 shim 的 serve 路由 → 两个 shim 测试失败(
expected … to contain 'if "%~1"=="serve" goto serve'/'if [ "${1:-}" = "serve" ]')。 - 从
DIST_REQUIRED_PATHS移除cli-entry.js→requires the standalone cli-entry wrapper失败(expected [Function] to throw)。 - 移除 npm-only 跳过逻辑 →
skips npm-only artifacts失败(Unexpected dist asset: …/dist/patches)。
观察项(不阻塞合并)
serve现在不带--expose-gc—— 这是有意为之,使 standaloneserve与 npm 安装版serve路径一致(npm 的bin本就是cli-entry.js)。global.gc()的唯一使用方是memoryPressureMonitor的trigger_gc,它有typeof global.gc === 'function'守卫并降级为告警;channel 子进程仍通过 ACP bridge(spawnChannel.ts)显式获得--expose-gc。即 standalone daemon 主进程失去了显式trigger_gcGC —— 值得维护者明确认可,但与 npm 行为一致且已优雅处理。- 维护耦合:
DIST_NPM_PACKAGE_ONLY_ENTRIES(postinstall.js、patches)必须与prepare-package.js实际产出的 npm-only 文件保持同步。若将来新增 npm-only 产物却未更新此集合,打包会以Unexpected dist asset显式失败、而非错误打包 —— 默认行为是安全的。 - Windows shim 通过内容断言 + 逻辑复核验证(非 serve 行之后的
exit /b %ERRORLEVEL%正确防止落入:serve);未实际执行(无 Windows 主机),与 PR 自身的状态表一致。
结论
LGTM —— 建议合并。 路由在运行时正确且稳定,内容打包 / 跳过 / 必需产物校验均成立,新增测试也确实守住了这些行为。唯一未通过的测试是环境构建产物缺失,并非代码问题。
Verified locally in an isolated worktree at ecba1e90b with a real standalone build + tmux runtime execution + mutation testing.
What this PR does
Standalone archives now route
qwen servethrough the packaged CLI entry wrapper while keeping other standalone commands on the existing directnode --expose-gc lib/cli.jspath. The standalone package builder also treats the entry wrapper as a required runtime artifact so release archives cannot be produced without the file that the serve fast path needs. Whenprepare:packagehas staged npm-only artifacts indist/, standalone packaging now skips those artifacts instead of failing or copying them into the archive.Why it's needed
Recent daemon startup work optimized the npm-installed
cli-entry.jspath, but the standalonebin/qwenandbin/qwen.cmdshims still invokedlib/cli.jsdirectly with--expose-gc. That bypassed the compile-cache and serve fast-path behavior for standalone users, leaving daemon startup slower in the installation mode used by hosted standalone releases. Requiringcli-entry.jsalso needs to work with the documentedbundle -> prepare:package -> package:standaloneflow, whereprepare:packagewrites npm package-only files that standalone archives should not ship.Reviewer Test Plan
How to verify
Build a standalone archive and inspect the generated launchers.
qwen serveshould executelib/cli-entry.js, while non-serve commands should still executenode --expose-gc lib/cli.js. The package builder should fail ifdist/cli-entry.jsis missing, and it should still package successfully whendist/contains npm-onlypostinstall.jsandpatchesartifacts fromprepare:packagewithout copying those artifacts intolib/.Evidence (Before & After)
N/A - non-UI packaging fix. The regression coverage asserts the generated Unix and Windows archive shims, the missing-entry failure path, and the prepared
dist/case with npm-only artifacts.Tested on
Environment (optional)
macOS local worktree with Node.js v26.0.0. Dependencies installed with
npm ci --ignore-scripts --no-audit --progress=falsein a clean worktree before validation.Risk & Scope
qwen serve, so commands with flags before the subcommand continue to use the existing fallback behavior. Standalone archives intentionally skip npm-only postinstall artifacts because no npm install lifecycle runs inside the archive.getCliVersion()after listen and does not change ACP bridge import timing; those remain separate startup follow-ups.--expose-gcpath.Linked Issues
Relates to #4748
中文说明
What this PR does
Standalone 包现在让
qwen serve通过打包进去的 CLI entry wrapper 启动,同时保持其它 standalone 命令继续走现有的直接node --expose-gc lib/cli.js路径。standalone 打包脚本也把这个 entry wrapper 设为必需运行时产物,避免生成缺少 serve fast path 入口文件的发布包。当prepare:package已经在dist/中放入 npm-only artifacts 时,standalone 打包现在会跳过这些文件,而不是失败或把它们复制进 archive。Why it's needed
近期 daemon 启动优化已经覆盖 npm 安装的
cli-entry.js路径,但 standalone 的bin/qwen和bin/qwen.cmd仍然直接带--expose-gc调用lib/cli.js。这会让 standalone 用户绕过 compile cache 和 serve fast path,导致 hosted standalone release 使用的安装方式里 daemon 启动仍然偏慢。把cli-entry.js设为必需文件后,也需要兼容文档里的bundle -> prepare:package -> package:standalone流程,因为prepare:package会写入 standalone archive 不应该携带的 npm package-only 文件。Reviewer Test Plan
How to verify
构建 standalone archive 并检查生成的启动脚本。
qwen serve应该执行lib/cli-entry.js,非 serve 命令应该仍然执行node --expose-gc lib/cli.js。如果缺少dist/cli-entry.js,打包脚本应该失败;当dist/中含有prepare:package生成的 npm-onlypostinstall.js和patchesartifacts 时,打包应该仍然成功,并且这些 artifacts 不会被复制进lib/。Evidence (Before & After)
N/A - 非 UI 的打包修复。回归测试会断言生成的 Unix 和 Windows archive shim 内容、缺失 entry wrapper 时的失败路径,以及准备过的
dist/中含 npm-only artifacts 的场景。Tested on
Environment (optional)
macOS 本地干净 worktree,Node.js v26.0.0。验证前在干净 worktree 里使用
npm ci --ignore-scripts --no-audit --progress=false安装依赖。Risk & Scope
qwen serve,所以子命令前带 flag 的调用会继续使用现有 fallback 行为。standalone archive 会有意跳过 npm-only postinstall artifacts,因为 archive 内部不会运行 npm install lifecycle。getCliVersion()移到 listen 之后,也不修改 ACP bridge 的导入时机;这些仍是独立的启动优化后续项。--expose-gc路径。Linked Issues
Relates to #4748