Repository navigation
perf(dev): add pnpm worktree bootstrap foundation - #10449
Conversation
Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
Validate pnpm lock updates in releases and exercise real installs and builds across supported hosts. Preserve npm release compatibility and keep dependency-only setup from rewriting npm-layout notices. Co-authored-by: Qwen-Coder <[email protected]>
Stage 1 test report (updated for
|
| Dependency path | Elapsed | Additional worktree disk |
|---|---|---|
| npm, warm cache | 27.13 s | 1,472.18 MiB |
| pnpm, warm shared store | 22.31 s | 98.69 MiB |
- Additional disk usage: 93.3% lower with pnpm (14.9× smaller).
- A second warm pnpm bootstrap completed in 3.48 s.
- A separate eager repository build measured 128.57 s; Stage 1 intentionally skips that work and does not claim pnpm build support.
Scope and compatibility checks
- Existing npm build, release workflow, versioning script, npm lockfile, and VS Code package manifest have zero diff from current
main. - Internal workspace versions are normalized only for pnpm resolution. As a release-version simulation, changing the channel-base manifest version from
0.22.3to9.99.9still passedpnpm install --lockfile-only --frozen-lockfile --ignore-scriptsin 362 ms, without regenerating the pnpm lockfile. - The real bootstrap was observed taking the offline path first and correctly falling back to the registry when the shared store lacked a newly added dependency. The local registry then repeatedly timed out on unrelated cross-platform optional binaries; the run was stopped after 195.83 s. The three-OS clean-install workflow below is the authoritative networked gate.
Local verification
| Check | Result |
|---|---|
| Worktree/bootstrap and workflow tests | 27/27 passed |
| VS Code notice-generation tests | 24/24 passed |
| Workflow size tests | 175 passed, 23 skipped |
| Frozen offline lockfile validation | passed, 413 ms |
| Prettier | passed |
| yamllint | passed |
| actionlint | passed |
| Final diff whitespace check | passed |
CI gate
The real frozen bootstrap and clean-worktree check passed on all three hosts at the current head in run 33256922709:
| Host | Result | Duration |
|---|---|---|
| Linux | passed | 59 s |
| macOS | passed | 1 m 35 s |
| Windows | passed | 2 m 5 s |
The unchanged npm path also passed dependency installation, the critical runtime dependency audit, lockfile validation, lint/format/schema checks, and the 152-test no-AK integration gate in Qwen Code CI. The Java daemon E2E independently completed npm installation, a full Qwen Code build, bundling, and daemon E2E in SDK Java. Two initial Ubuntu Java timing-test failures passed on the targeted rerun, leaving the SDK Java workflow green. These are direct checks that Stage 1 leaves the existing npm build path operational.
The preceding run exposed a real nested-lifecycle issue: npm run replaced npm_lifecycle_event, causing notice generation to rewrite a tracked file. The fix uses a bootstrap-scoped guard instead; the explicit notice-generation path remains unchanged. The corrected behavior passed locally through the real nested npm command and in all three clean-worktree CI jobs. Build and release jobs remain the repository's existing npm-based gates.
第一阶段测试报告
本报告只覆盖收窄后的第一阶段:为额外 worktree 提供可选的低磁盘依赖安装。pnpm 构建/CI 迁移属于第二阶段;发布安装、打包和 publishing 属于第三阶段。
- 同提交、同 APFS 卷基准:npm warm-cache 为 27.13 秒 / 1,472.18 MiB,pnpm warm-store 为 22.31 秒 / 98.69 MiB。
- pnpm 的额外磁盘占用降低 93.3%,约小 14.9 倍;第二次 warm bootstrap 为 3.48 秒。
- 当前 npm 构建、release workflow、版本脚本、npm 锁文件及 VS Code package manifest 相对最新
main零差异。 - 将内部 channel-base 版本从
0.22.3模拟改为9.99.9后,pnpm frozen lock 检查仍在 362 ms 内通过,不需要发布脚本刷新 pnpm lock。 - 本地定向测试 51/51 通过,workflow size 测试 175 通过 / 23 跳过,冻结锁、格式、YAML 与 workflow 静态检查全部通过。
- 真实安装已验证离线优先与缺包时 registry fallback;本地 registry 在下载其他平台可选二进制时持续超时,运行于 195.83 秒停止,因此最终 clean install 以 PR 的 Linux/macOS/Windows 三系统 CI 为准。
- 当前 head 的最终三系统 CI 已全部通过:Linux 59 秒、macOS 1 分 35 秒、Windows 2 分 5 秒,且安装后 worktree 均保持干净。
- 未改动的 npm 路径已在 CI 中通过依赖安装、关键运行时依赖审计、lockfile、lint/format/schema 检查和 152 项 no-AK 集成测试;Java daemon E2E 也通过了 npm 安装、完整构建、bundle 和 daemon E2E。两个 Ubuntu Java 时序测试的首次失败在定向重跑后均通过,SDK Java 工作流最终全绿。这些结果直接证明阶段 1 没有破坏现有 npm 构建链路。
Keep Stage 1 limited to low-disk worktree dependency installation while leaving npm build and release paths unchanged. Normalize internal pnpm workspace dependencies so release version bumps do not stale the pnpm lockfile.\n\nCo-authored-by: Qwen-Coder <[email protected]>
Record the required attribution for the Stage 1 merge without rewriting published history. Co-authored-by: Qwen-Coder <[email protected]>
Use a bootstrap-scoped notice guard because nested npm lifecycle commands replace npm_lifecycle_event. This preserves explicit notice generation while preventing dependency setup from rewriting tracked output. Co-authored-by: Qwen-Coder <[email protected]>
Register the new workflow in the repository size ratchet as required by the main CI gate. Co-authored-by: Qwen-Coder <[email protected]>
|
CI attribution for run 33256922668: the Test (ubuntu-latest, Node 22.x) job failed on the 1h job timeout ("The job has exceeded the maximum execution time of 1h0m0s", job 99134467834, 1h0m26s) — not on a test assertion. The downstream failures (coverage upload "No files were found", junit parse "Cannot read properties of null", Post Coverage Comment artifact-not-found) all stem from the job being killed mid-run. web-shell E2E Smoke hit the same 20m timeout. This is the same infra/baseline job-timeout signature seen across this window (#9531, #10394, #9260) and is unrelated to the PR diff. Rerunning the failed jobs. |
The lockfile was generated before the WebShell cutover (#9811) removed @qwen-code/webui from web-shell and the tailwind tooling plus @qwen-code/webui from vscode-ide-companion, so frozen-lockfile installs fail on all three smoke platforms with ERR_PNPM_OUTDATED_LOCKFILE. Drop the five stale importer entries so the lockfile matches the current package.json manifests; verified with pnpm 11.24.0 install --frozen-lockfile --lockfile-only. Co-authored-by: Qwen-Coder <[email protected]>
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. |
…ootstrap # Conflicts: # scripts/tests/package-scripts.test.js
The merge of origin/main added remend@^1.3.1 to packages/cli/package.json without updating pnpm-lock.yaml, breaking the pnpm Worktree Smoke workflow frozen-lockfile install. Regenerated with pnpm install. Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-conflict/jmtid3rse9h
…nto prmerge-10449
A spread of process.env is an ordinary object, and native Windows shells expose the path variable as `Path`, so `env.PATH` was undefined there and findOnPath never located corepack — the offline-first Corepack bootstrap silently degraded to npx on exactly the hosts it exists for. Co-authored-by: Qwen-Coder <[email protected]>
- Split the fallback log on /\r?\n/ like the sibling cmd.exe mocks, so the assertion holds on the Windows lane's CRLF output. - Add a win32 variant bootstrapping with the native `Path` casing to pin the case-insensitive lookup. - Assert the smoke workflow's fail-fast flag, the install-before-clean step order, and the no-build guard via a substring on the raw job text. - Extend the pnpmfile rewrite fixture to devDependencies and optionalDependencies, which the committed lockfile already uses. - Assert the notice-skip guard by effect (writeFile never called) and add the flag-absent companion test with I/O stubbed. Co-authored-by: Qwen-Coder <[email protected]>
|
@qwen-code /triage |
1 similar comment
|
@qwen-code /triage |
|
CI attribution for head fd43387 — run 33527752425,
Conclusion: infra-caused (runner saturation + wall-clock), not PR-caused — no code change on this PR for it. Leaving the rerun decision to maintainers. |
|
@qwen-code /triage |
Co-authored-by: Qwen-Coder <[email protected]> Patrol-Run: qwen-pr-conflict/jmtjyz3tdco
Main removed the webui and cua-driver packages; drop both from the pnpm rewrite set and regenerate the pnpm lockfile against the merged manifests (picks up playwright, react-markdown, and the other main-side dependency changes) so the frozen bootstrap matches what the PR merge ref will run.
There was a problem hiding this comment.
Reviewed at e64910a after the latest main merge. The earlier companion-dependency, core @types/node, and stale-lock blockers are fixed; the new pnpm smoke run now passes on all three OSes, and the targeted script/notices tests pass locally.
Two P1 findings remain inline: the npx fallback bypasses the committed package-manager integrity pin, and the workflow is deterministically red under this repository's YAML lint rule.
One non-blocking parity item should be carried into the next lock refresh: pnpm-workspace.yaml still omits main's react-markdown: '^9.0.0' override, so the pnpm graph retains [email protected] that main intentionally deduplicated. This is deferred rather than widening a seventh review round.
Ponytail, also deferred to follow-up:
.pnpmfile.mjs:7:mirror— the 26-name registry plus its filesystem reconciliation test is about 80 lines for three current dependency shapes. Match scopedfile:, scoped*, and the exact-version@qwen-code/channel-basecase directly.scripts/check-lockfile.js:78:native— the pnpm smoke already runs a frozen install and performs pnpm's native supply-chain validation for every pnpm input change. Delete the duplicate YAML validator and mirror test (~55 lines); if retained, its git/tarball exemption must require immutable hashes.
net: ~-135 lines possible.
|
Update on the outstanding review findings, addressed in 317565f + e64910a (head e64910a): Criticals
Smaller items
Verification: Note: after merging current main into the branch (main had added Left as follow-ups (per the earlier deferrals): CVE/dependabot coverage for the pnpm lockfile, the broad registry-retry exit-code condition, and Windows-only test lanes. |
|
Resolved the two P1 findings from review
Verification: package-script tests 38 passed / 1 Windows-only skipped, Prettier passed, yamllint passed, syntax check passed, and a real frozen bootstrap completed from the warm pnpm store with a clean tracked tree. The non-blocking |
qwen-code-dev-bot
left a comment
There was a problem hiding this comment.
Approved at head d894f363.
我这一轮先是把 16550954 上的那条 P1 当阻塞项核对的,而 d894f363 在我复核期间把它按建议的方式修掉了,所以现在的结论是通过。核对链(全部在这个 head 上读到):
- 之前的问题:
package.json:4的 pin 带+sha512.bd27e3…,但旧代码用packageManager.replace(/\+sha512\.[0-9a-f]{128}$/, '')生成 npx spec,并在findOnPath(corepack)落空时走npx --yes <版本spec>—— 也就是把仓库提交的那份字节校验静默丢掉再去执行第三方代码。 - 现在:
scripts/setup-worktree.js:47-54改成找不到 corepack 就直接process.exit(1)并报Corepack is unavailable,runPnpm只剩spawnSync(corepack, ['pnpm', ...args])一条路径,文件里npx相关代码全部删除(我 grep 过,一处不剩)。:19保留的getPinnedPnpmPackage(...)不是死代码:scripts/pnpm-package.js:7-17在 pin 不是精确 pnpm 版本时抛错,所以它仍是执行前的入口校验。 - 这条改动被测试钉住了:新增
fails closed when Corepack is unavailable断言status === 1且 stderr 含Corepack is required;原先把"去掉 suffix 的 spec"钉住的日志用例改成对corepack断言,占位参数也从$5/%5同步移到$4/%4,说明改的是真行为不是改断言凑绿。设计文档里"回退到 npm 自带的 npx"那段也一并改成了 fail-closed 的描述,没有留下互相矛盾的文档。
这一轮我在这个 PR 上另外核对过、并且确认成立的部分(16550954 与 d894f363 对这些文件没有差异):历史两条 Windows 相关 Critical 都在代码里落地了——findOnPath 走 pathValue() 做 Path/PATH 大小写不敏感查找(:31-45,注释也解释了为什么),几处多行日志断言都改成 split(/\r?\n/);.pnpmfile.mjs 的 readPackage 覆盖 dependencies/devDependencies/optionalDependencies 三个字段且工作区包名集合被测试钉住;新增的 pnpm-worktree-smoke.yml 用的是 pull_request(不是 pull_request_target),fork PR 拿不到写权限 token;scripts/check-lockfile.js 现在要求 pnpm lock 里每个 registry resolution 都带 sha512- integrity;AGENTS.md 明确 npm 仍是 build/CI/打包/发布的权威路径、pnpm 这套只做 install,这个范围划分让 2 万行 lockfile 可控。另外那条还挂着未 resolved 的 yamllint P1(三个标量要加引号)在 16550954 上其实已经改好并且 Lint & Static 绿了,可以直接关掉。
不阻塞的一条卫生建议:pnpm-package.js:13 的正则把 +sha512… 写成可选,所以现在把 pin 的校验值删掉仍然不会红,而这条 PR 的立意正是钉住 pnpm 的字节。既然执行路径已经收成"必须有 Corepack",不妨在这个入口把 suffix 要求成必填(顺手在 pnpm-worktree-smoke.yml 或 check-lockfile 里加一条断言即可)。
CI 事实。d894f363 是几秒前刚推的,所有 lane 都还在 pending(Test (ubuntu)、Lint & Static、Integration (no-AK)、三条 Install、Java 各腿、delay-automatic-review),没有任何一条红。上一个 head 16550954 上主 lane 是绿的(Test 20m12s、Lint 15m46s、no-AK 8m59s、两条 Desktop Shell),web-shell smoke 当时还在跑,本轮增量只有 3 个文件(脚本 + 测试 + 文档)。页面上那张 CHANGES_REQUESTED 是 ci-bot 落在 def9ec3955 的旧票,两轮已过;如需清掉陈票,重跑一次 @qwen-code /triage 就行。
chiga0
left a comment
There was a problem hiding this comment.
Review — pnpm worktree bootstrap (Stage 1)
Tier: Standard. The PR adds new install/build scripts and CI; existing npm paths are unchanged. Opt-in, reversible.
Scope: Source files, scripts, and CI workflow reviewed. NOT reviewed: pnpm-lock.yaml (21,600 lines of generated content — integrity validated by the check-lockfile.js test, which the test suite runs end-to-end). generate-notices.test.js snapshot section: title only. No working tree available for live pnpm install; rung 3 not applicable (no terminal-environment production behavior changed).
Checked
Class 1 — Contract asymmetry
generate-notices.jsrefactored:runNoticeGeneration(env)wrapsmain()with a skip guard. Both the skip-and-return path and the run-through path are tested; thefs.writeFilespy confirms the early return doesn't just log — it suppresses the write.packages/vscode-ide-companion/package.jsonadds@qwen-code/qwen-code-core: "*". The correspondingpackage-lock.jsonentry is present in the diff; the smoke workflow's workspace-link resolve step confirms pnpm materializes it.
Class 2 — API / compatibility
packageManagerfield added to rootpackage.json. Corepack reads it; npm ignores it. Engines already require Node ≥ 22, which bundles Corepack. No npm install path changes.packages/core/package.jsonpins@types/nodeto20.19.1. Matches the npm-hoisted version confirmed by the design doc; the npm lockfile is updated accordingly.
Class 3 — Error handling
setup-worktree.jsretry logic: offline returns exit 0 → success; signal/error/status ≥ 128 → fatal viaexitWithResult; other non-zero status → online fallback. The?? 1inexitWithResultguards the null-status-no-signal edge case (spawnSync cannot produce this in practice; the guard is a correct safety net).- Windows PATH case-sensitivity:
pathValue()usesObject.keys(env).find(n => n.toUpperCase() === 'PATH')— handles bothPATHandPath. Test covers this with an explicitdelete env.PATH+env.Path = ...fixture.
Class 5 — Test validity
- Bootstrap test: mock corepack logs env vars and args to a file; the test reads the file and checks exact content. Deleting
QWEN_SKIP_PREPAREor--frozen-lockfilefrom the script would break the assertion. - Fallback test: mock exits 1 on
$4 == --offline, 0 otherwise. The log captures both calls in order. Split uses/\r?\n/— handles CRLF on Windows. - Interrupt test:
kill -INT $$producessignal = SIGINT,exitWithResultcomputes128 + 2 = 130. Windows mock usesexit /b 130directly. Both paths to exit 130 exercised.
Class 10 — Stated intent vs. code
- Design doc states: "internal workspace dependencies are normalized only in pnpm's in-memory resolution". Confirmed:
.pnpmfile.mjshook rewrites atreadPackagetime only; checked-in manifests and npm lockfile are untouched. - Design doc states: "fails closed when Corepack is unavailable". Confirmed:
findOnPathreturnsundefined→process.exit(1)before any install attempt. - CI workflow
cancel-in-progresscorrectly set tofalsefor post-merge push runs,truefor PR runs. Test pins this expression.
Cross-check against prior reviews
Prior reviewer (qwen-code-ci-bot) filed two critical findings:
- R1-1: Log assertion split only on
\n→ confirmed addressed in current head: uses/\r?\n/split. - R3-1:
findOnPathreadenv.PATHwithout Windows case folding → confirmed addressed:pathValue()iteratesObject.keyswith.toUpperCase()comparison.
Suggestion-level findings R1-2 through R1-8 are each addressed in the current code (write-spy added, all three dep fields exercised, etc.) or represent non-blocking style preferences.
No blocking findings.
Approval blockers: none.
Reviewed with AI assistance.
yiliang114
left a comment
There was a problem hiding this comment.
Review at head d894f363fa (merge-base a1cbe75cf1). No blocking findings. 8 inline: 1 P1 worth landing now, 2 P2, 5 P3 — the P2/P3 are deferrable under the AGENTS.md 5-round posture.
Disposition of the outstanding Critical (R5-1). packages/vscode-ide-companion could not resolve @qwen-code/qwen-code-core under pnpm's hoisted linker — fixed at this head. The manifest declares "@qwen-code/qwen-code-core": "*" (packages/vscode-ide-companion/package.json:291), .pnpmfile.mjs:32 carries that exact name so the hook rewrites it to workspace:*, pnpm-lock.yaml:1176-1178 records version: link:../core, and package-lock.json gained the matching importer line so npm ci stays installable. The prescribed acceptance witness exists and is not vacuous: pnpm-worktree-smoke.yml:86-90 resolves the specifier from the companion, pinned by scripts/tests/package-scripts.test.js:427-433; packages/core does export ./package.json; and Install (ubuntu|macos|windows-latest) are all SUCCESS at this head.
Round-5 probes verified closed at this head (read at the cited lines, not re-reported): spawnSync cwd (now cwd: rootDir, :56-57); the corepack→npx ENOENT fallback (replaced by a fail-closed Corepack requirement, :47-53); the pnpm-package.js pin regex (now accepts the +sha512.<128 hex> digest corepack writes, :12-16); the missing pnpm integrity gate (scripts/check-lockfile.js:78-113, wired into npm run check:lockfile); .prettierignore coverage of pnpm-lock.yaml; the .pnpmfile.mjs workspace set (now includes @qwen-code/web-shell, drops @qwen-code/webui, and is reconciled against the manifests by a test at :125); unconditional cancel-in-progress (now PR-only, :53-57); patch-package corrupting the store (packageImportMethod: 'clone-or-copy' with an inline rationale naming ERR_PNPM_NO_OFFLINE_TARBALL); the npm run build needle gap (now substring-checks both spellings, :462-467); and contributor-facing documentation of the bootstrap (AGENTS.md:90-96).
Not verified by execution — disclosed gap. No local install, build, or test run was possible: this head has no node_modules in any local worktree and the machine's root filesystem is at 100% (765 MB free). Test (ubuntu-latest, Node 22.x), Lint & Static, and Integration Tests were still IN_PROGRESS at review time; 17 checks SUCCESS, 0 failures. Everything below is evidenced from the pinned sources and the two committed lockfiles, not from an executed install — in particular the P1 override drift is read off the lockfiles rather than observed in a materialized tree.
Maintainer verification on real hardware — head
|
| check | result |
|---|---|
node scripts/setup-worktree.js, cold machine (empty store) |
exit 0, 69.3 s |
| same, warm store, host SSD | exit 0, 16.5 s |
| documented offline-first fallback | real: --offline → ERR_PNPM_NO_OFFLINE_META, Cached install unavailable; retrying with registry access., --prefer-offline succeeds |
QWEN_SKIP_PREPARE guard |
Skipping prepare build/bundle/husky because QWEN_SKIP_PREPARE is set. |
QWEN_SKIP_NOTICE_GENERATION guard |
Skipping VS Code notice generation during worktree bootstrap. |
git status --porcelain after bootstrap |
empty — cold and warm, on APFS and on HFS+ |
require.resolve('@qwen-code/qwen-code-core/package.json', {paths:['packages/vscode-ide-companion']}) |
resolves to packages/core/package.json |
| release-version independence | bumped @qwen-code/channel-base 0.23.0 → 0.99.0-rc.1 in packages/channels/telegram/package.json; frozen install still exits 0 (Lockfile is up to date, resolution step is skipped). Lock importers record specifier: workspace:*, so the rewrite is baked in |
| idempotency | three back-to-back bootstraps, all exit 0, 1150 top-level node_modules entries before and after, tree clean |
repo-wide yamllint -c .yamllint.yml |
0 findings — the earlier YAML P1 is genuinely fixed. The pnpm-lock.yaml ignore is load-bearing: 11 733 findings without it |
scripts/tests/package-scripts.test.js on macOS (a lane CI skipped) |
39 tests, 38 passed / 1 skipped (the win32-only pathValue case) — no macOS-specific failure |
The full test:scripts run on macOS was 2203 passed / 15 failed / 39 skipped; every one of the 15 failures is Failed to resolve entry for package "@qwen-code/qwen-code-core", i.e. unbuilt workspace packages — see F1, not a defect in this PR's own tests.
2. The headline disk claim, measured
| pnpm bootstrap, APFS | pnpm bootstrap, HFS+ | npm ci, APFS |
|
|---|---|---|---|
| shared store / cache, one-time | 1.067 GiB | 1.067 GiB | 0.233 GiB |
| node_modules, physical | 33.6 MiB | 1.264 GiB | 1.337 GiB |
| node_modules, apparent | 1.263 GiB | 1.264 GiB | 1.289 GiB |
| block sharing achieved | 38.4× | 1.00× | 1.00× |
| warm install, same disk image | 65.4 s | 164.3 s | 159.8 s |
Both install arms in the last row run inside the disk image, which roughly quadruples wall time against the host SSD — the ratio is the meaningful part, not the absolute seconds.
On APFS the PR is better than advertised — 33.6 MiB against the claimed ≈99 MiB, and 16.5 s on the host SSD against the claimed ≈22 s. pnpm also beats npm ci on wall time by 2.4× on identical media.
On a filesystem without copy-on-write the saving is gone: packageImportMethod: clone-or-copy degrades to a plain copy and a worktree costs 1.264 GiB — sharing ratio exactly 1.00×. Break-even against npm moves from N = 1 worktree to N ≈ 12. This is the same axis as the deferred D7-24; it is now measured rather than argued. docs/design/…-pnpm-worktree-bootstrap.md does say "APFS volume"; AGENTS.md:93 drops that qualifier.
3. F1 — the bootstrapped worktree cannot build, and neither can the documented lockfile refresh
packages/acp-bridge declares no @types/node, so it takes the root hoist — 20.19.1 under npm, 22.20.1 under pnpm. At src/process-registry.ts:744 (return result.stdout; from an encoded spawnSync) @types/node 22 widens the type to string | NonSharedBuffer:
$ npm run build # in the bootstrapped worktree
src/process-registry.ts(744,3): error TS2322: Type 'string | NonSharedBuffer' is not assignable to type 'string'.
npm error workspace @qwen-code/[email protected]
real 117.92 BUILD_RC=1
Same-tree A/B, changing nothing but that one directory: with @types/[email protected] shadowed into packages/acp-bridge/node_modules, npm run build completes, rc=0, 168.6 s, git status clean. The counterfactual npm tree at the same commit also builds, rc=0. So this is specific to the pnpm layout, and it is the only thing standing between Stage 1 and a fully usable worktree.
It also breaks the documented maintenance path. AGENTS.md:95 says to regenerate with corepack pnpm install; that runs the root prepare, which builds, which dies on the same error:
$ corepack pnpm install
. prepare: src/process-registry.ts(744,3): error TS2322: …
[ELIFECYCLE] Command failed with exit code 1. REGEN_RC=1
(It dies before touching NOTICES.txt, so the tracked file is not degraded and the tree stays clean — I could not reproduce that part of D7-2.)
CI cannot see any of this: the smoke workflow installs and then checks one require.resolve plus git status, and the PR's own test asserts the job must contain no build (expect(installJob).not.toContain('npm run build')).
Fix: add "@types/node": "20.19.1" to packages/acp-bridge/package.json — exactly the pattern already applied to packages/core. It is not free: I verified that a frozen install after that manifest edit correctly refuses with ERR_PNPM_OUTDATED_LOCKFILE, so both lockfiles must be regenerated with it.
4. F2 — the second lockfile is 6 published advisories behind, and nothing reads it
Census of the two lockfiles at this head: 1 649 package names appear in both, 57 resolve to a different version set, 5 of them older on the pnpm side:
| package | package-lock | pnpm-lock | advisories only in the pnpm tree |
|---|---|---|---|
fast-uri |
3.1.7 | 3.1.5 | 4 × HIGH, all published 2026-09-02, all fixed in 3.1.6 |
qs |
6.16.0 | 6.15.2 | 2 × MEDIUM, fixed in 6.16.0 |
react-devtools-core |
7.0.1 | 6.1.5 | none |
side-channel |
1.1.1 | 1.1.0 | none |
side-channel-list |
1.0.1 | 1.0.0 | none |
(fast-uri: GHSA-5jgf-p345-68v8, GHSA-f65p-4m7j-42xc, GHSA-fph4-wmhf-6fwf, GHSA-jqff-g426-hqxp. qs: GHSA-x5fp-wj9c-mxmx, GHSA-4mjr-xmp4-gh2g. Confirmed against the GitHub Advisory API today.) This is R7-1 plus D7-9, now with the whole population counted rather than one finding.
No gate reads the new file: scripts/audit-runtime-critical.js and security-checks.yml both run npm audit against package-lock.json, dependabot.yml has no pnpm ecosystem, and pnpm's own supply-chain policy check ran and passed on all 1 967 entries — it is not an advisory gate.
The good news: both are transitive with caret ranges ([email protected] → fast-uri: ^3.0.1, [email protected] → qs: ^6.15.2), so the lockfile regeneration that F1 already forces will pick up 3.1.7 and 6.16.0 by itself. One action closes both blockers.
Worth deciding separately: whether a pnpm audit lane and a pnpm dependabot ecosystem land now or in Stage 2. Until one of them exists, the pnpm lock drifts silently by construction.
5. F3 — the new pnpm branch of scripts/check-lockfile.js gives the wrong verdict on 5 of 6 inputs
I fed the script hand-built lockfiles (same scripts/, same package-lock.json, only pnpm-lock.yaml swapped):
pnpm-lock.yaml |
exit | printed |
|---|---|---|
| the real lockfile (control) | 0 | passed ✅ |
no packages: section |
0 | pnpm lockfile check passed. — 0 entries examined |
| empty file | 0 | pnpm lockfile check passed. — 0 entries examined |
| remote tarball, no integrity | 0 | passed — false green |
| git resolution, no commit hash | 0 | passed — false green |
legitimate type: directory entry |
1 | rejected — false red |
The suite only pins the control row, so none of the five wrong verdicts is caught. The real lockfile has 0 directory entries today, so the false red is latent. Either tighten the branch (require an immutable hash for the git/tarball exemption, fail when zero entries were examined) or drop it — @yiliang114's own round-7 note already floats deleting it.
6. Smaller things
.pnpmfile.mjsis hashed into the lockfile (pnpmfileChecksum: sha256-YMmpx+…). I appended a single comment line and every bootstrap failed withERR_PNPM_LOCKFILE_CONFIG_MISMATCH. That is correct pnpm behaviour, but nothing inAGENTS.mdor the design doc warns that a comment-only edit to that file stales the lock (D7-17 — reproduced).- The offline→registry retry misclassifies non-cache failures.
setup-worktree.jsretries with--prefer-offlineon any non-fatal, non-zero exit. Two real reproductions (ERR_PNPM_LOCKFILE_CONFIG_MISMATCHabove, andERR_PNPM_OUTDATED_LOCKFILEfrom the F1 fix attempt) each ran the whole install twice and needed network access to fail the second time. Gating the retry on the cache-specific error codes would make offline failures honest and halve the time to the message. - 5 ×
[WARN] Failed to create bin … qwen-serve-mcp … ENOENT … @qwen-code/sdk/dist/daemon-mcp/serve-bridge/bin.json every bootstrap, because the sdk is unbuilt at install time.npm ciprints none. Cosmetic today; it disappears once F1 makes a build possible. - I ran Node 24.18.1, not CI's 22.x. Everything above reproduced on that runtime; the three-OS smoke lane remains the authority for 22.x/Windows/Linux.
7. Recommendation
The design is sound, the guards work, the frozen-lock/version-independence trick does what it claims, and on APFS the numbers are better than the PR advertises. I would land it after:
- Add
"@types/node": "20.19.1"topackages/acp-bridge/package.jsonand regenerate both lockfiles. This makes the bootstrapped worktree buildable, un-breaks the documentedcorepack pnpm installrefresh, and sweeps upfast-uriandqsin the same commit — closing F1 and F2 together. - Qualify the disk claim in
AGENTS.md:93with the copy-on-write condition the design doc already states, e.g. "≈99 MiB on a copy-on-write filesystem (APFS, btrfs, XFS with reflink); on ext4 or any filesystem without reflink the worktree still costs ≈1.2 GiB".
F3 and the .pnpmfile note are worth a follow-up but I would not hold the merge for them. Not verified here: Windows, Linux, the workflow's behaviour inside GitHub Actions, and anything on the release/publish path.
中文说明
维护者本地真机验证 —— head d894f363
我没有只读 diff,而是在本地把 Stage 1 的链路真实跑通了一遍。机制是有效的,收益也是真的,但合并前建议先落两件事,而这两件事其实一个动作就能一起解决。
验证台:macOS 26.6.2(arm64)、Node 24.18.1、corepack 0.35.0、pnpm 11.24.0(由提交里的 packageManager pin 拉取)。用 PR head 的独立 git clone(不是共享 worktree),外加两个专用稀疏磁盘映像——一个 APFS、一个 HFS+——每个都把 $HOME 重定向到映像内部,使 pnpm store、pnpm 元数据缓存、npm 缓存全部落在被测卷上。每个 worktree 的开销用 df -k 已用块差值统计,不是 du。
1. 确认可用的部分
冷机 node scripts/setup-worktree.js 退出码 0、69.3 秒;warm store 在宿主 SSD 上 16.5 秒。文档里的「先离线、失败再联网」回退是真的:--offline 报 ERR_PNPM_NO_OFFLINE_META,脚本打印 Cached install unavailable; retrying with registry access.,--prefer-offline 成功。两个跳过开关都生效(跳过 prepare 构建/bundle/husky,跳过 VS Code notice 生成)。冷装、热装,在 APFS 和 HFS+ 上 git status --porcelain 都为空——这是 workflow 的核心判据,而 CI 从未跑过 macOS。工作区链接可解析。发布版本无关性成立:把 packages/channels/telegram 里的 @qwen-code/channel-base 从 0.23.0 改成 0.99.0-rc.1,冻结安装依然退出 0;锁文件 importers 里记录的就是 specifier: workspace:*。连跑三次幂等,树无变化。用仓库自己的 .yamllint.yml 全量 lint:0 条——之前那条 YAML P1 确实修好了;而且 pnpm-lock.yaml 的 ignore 是承重的,去掉后有 11 733 条。macOS 上跑 package-scripts.test.js(CI 跳过的泳道):39 个用例,38 过 1 跳(跳的是 win32 专属的 pathValue),没有 macOS 特有失败。
macOS 上 test:scripts 全量是 2203 过 / 15 失败 / 39 跳过,15 条失败全部是 Failed to resolve entry for package "@qwen-code/qwen-code-core",即工作区包未构建——见 F1,不是这个 PR 自己测试的问题。
2. 磁盘收益实测
| pnpm bootstrap / APFS | pnpm bootstrap / HFS+ | npm ci / APFS |
|
|---|---|---|---|
| 共享 store / cache(一次性) | 1.067 GiB | 1.067 GiB | 0.233 GiB |
| node_modules 物理占用 | 33.6 MiB | 1.264 GiB | 1.337 GiB |
| node_modules 表观占用 | 1.263 GiB | 1.264 GiB | 1.289 GiB |
| 实际块共享倍率 | 38.4× | 1.00× | 1.00× |
| 同一映像上的 warm 安装耗时 | 65.4 s | 164.3 s | 159.8 s |
最后一行两个安装臂都跑在磁盘映像里,相对宿主 SSD 大约慢 4 倍,有意义的是比值而不是绝对秒数。
在 APFS 上 比 PR 宣称的还好:33.6 MiB vs 宣称的 ≈99 MiB,宿主 SSD 上 16.5 秒 vs 宣称的 ≈22 秒;同介质下比 npm ci 快 2.4 倍。
但在没有 copy-on-write 的文件系统上收益消失:packageImportMethod: clone-or-copy 退化为纯拷贝,一个 worktree 要 1.264 GiB,共享倍率正好 1.00×。相对 npm 的盈亏平衡点从 N = 1 挪到 N ≈ 12。这与被延后的 D7-24 是同一条轴,现在是实测而不是推断。设计文档写了「APFS volume」,但 AGENTS.md:93 把这个限定词丢了。
3. F1 —— bootstrap 出来的 worktree 构建不了,文档写的锁文件重生成命令也跑不通
packages/acp-bridge 没有声明 @types/node,因此吃根部提升的版本——npm 下是 20.19.1,pnpm 下是 22.20.1。在 src/process-registry.ts:744(带编码的 spawnSync 的 return result.stdout;)上,@types/node 22 把类型放宽成 string | NonSharedBuffer,于是 npm run build 失败,退出码 1。
同一棵树做 A/B,只改这一个目录:把 @types/[email protected] 影子进 packages/acp-bridge/node_modules 后,npm run build 成功,rc=0,168.6 秒,git status 干净。同一 commit 的 npm 安装树对照臂也构建成功(rc=0)。所以这是 pnpm 布局特有的问题,也是 Stage 1 与「可用 worktree」之间唯一的障碍。
它还打断了文档写的维护路径:AGENTS.md:95 说用 corepack pnpm install 重生成锁文件,而该命令会跑根 prepare → 构建 → 死在同一个错误上,[ELIFECYCLE] 退出码 1。(它在写 NOTICES.txt 之前就死了,所以受跟踪文件没有被降级,树是干净的——D7-2 的那一半我没有复现出来。)
CI 看不到这些:smoke workflow 装完只检查一个 require.resolve 和 git status,而且 PR 自己的测试断言这个 job 不能包含构建步骤(expect(installJob).not.toContain('npm run build'))。
修法:给 packages/acp-bridge/package.json 加 "@types/node": "20.19.1"——就是 PR 已经对 packages/core 用过的那个套路。代价不是零:我验证过,改完 manifest 后冻结安装会正确地以 ERR_PNPM_OUTDATED_LOCKFILE 拒绝,所以两个锁文件都得重生成。
4. F2 —— 第二个锁文件落后 6 条已发布公告,而且没有任何门读它
对两个锁文件做全量普查:1 649 个包名同时出现在两边,57 个解析出的版本集合不同,其中 5 个在 pnpm 侧更旧:fast-uri 3.1.7 → 3.1.5(4 条 HIGH,均于 2026-09-02 发布,均在 3.1.6 修复)、qs 6.16.0 → 6.15.2(2 条 MEDIUM,6.16.0 修复)、react-devtools-core、side-channel、side-channel-list(后三者无公告)。这就是 R7-1 加 D7-9,只是这次数了全量而不是单点。
没有任何门读新文件:scripts/audit-runtime-critical.js 和 security-checks.yml 都是对 package-lock.json 跑 npm audit,dependabot.yml 没有 pnpm 生态,pnpm 自带的供应链策略检查跑了并且对全部 1 967 条通过——它不是公告门。
好消息:两者都是带 caret 范围的传递依赖([email protected] → fast-uri: ^3.0.1、[email protected] → qs: ^6.15.2),所以 F1 本来就要求的那次锁文件重生成会自动带上 3.1.7 和 6.16.0。一个动作同时关掉两个阻塞项。
另需单独决策:pnpm audit 泳道和 pnpm dependabot 生态是现在加还是留到 Stage 2。在其中之一存在之前,pnpm 锁的漂移在结构上就是静默的。
5. F3 —— 新增的 check-lockfile.js pnpm 分支在 6 种输入里有 5 种判定错误
只替换 pnpm-lock.yaml、其余不变地喂给脚本:真实锁文件(对照)退出 0 通过 ✅;没有 packages: 段 → 退出 0「通过」,实际检查了 0 条;空文件 → 退出 0「通过」,0 条;远程 tarball 且无 integrity → 退出 0,假绿;git 解析无 commit 哈希 → 退出 0,假绿;合法的 type: directory 条目 → 退出 1,假红。
测试只钉住了对照那一行,5 种错误判定一条都抓不到。真实锁文件今天没有 directory 条目,所以假红是潜伏的。要么收紧(git/tarball 豁免必须要求不可变哈希、检查 0 条时判失败),要么按 @yiliang114 第 7 轮自己提的那样删掉。
6. 其他
.pnpmfile.mjs的字节被哈希进了锁文件(pnpmfileChecksum)。我只追加了一行注释,此后每次 bootstrap 都以ERR_PNPM_LOCKFILE_CONFIG_MISMATCH失败。这是 pnpm 的正确行为,但AGENTS.md和设计文档都没有提醒「改一行注释就会让锁过期」(D7-17,已复现)。- 离线→联网的重试把非缓存类失败也当成缓存失败。
setup-worktree.js对任何非致命非零退出都用--prefer-offline重试。两次真实复现(上面的 checksum 不匹配,以及 F1 修法引发的ERR_PNPM_OUTDATED_LOCKFILE)都把整个安装跑了两遍,并且第二遍还要联网才能失败。把重试限定在缓存相关错误码上,离线失败才诚实,出错信息也能快一倍。 - 每次 bootstrap 都有 5 条
[WARN] Failed to create bin … qwen-serve-mcp …,因为安装时 sdk 尚未构建;npm ci一条都没有。今天只是观感问题,F1 修好后自然消失。 - 我用的是 Node 24.18.1,不是 CI 的 22.x。以上结论都在该运行时上复现;22.x/Windows/Linux 仍以三系统 smoke 泳道为准。
7. 结论
设计是站得住的,两个跳过开关有效,冻结锁 + 版本无关的做法确实做到了它宣称的事,APFS 上的数字比 PR 自己写的还漂亮。我建议按下面两步后合入:
- 给
packages/acp-bridge/package.json加"@types/node": "20.19.1"并重生成两个锁文件。 这一步让 bootstrap 出来的 worktree 可构建、让文档写的corepack pnpm install重生成路径恢复可用,并顺带在同一个 commit 里带上fast-uri与qs的修复版本——F1 和 F2 一起关闭。 - 给
AGENTS.md:93的磁盘结论补上 copy-on-write 限定词(设计文档里本来就写了),例如「在 copy-on-write 文件系统(APFS、btrfs、带 reflink 的 XFS)上约 99 MiB;在 ext4 等无 reflink 的文件系统上,worktree 仍然要约 1.2 GiB」。
F3 和 .pnpmfile 那条值得后续处理,但我不会为它们卡住合并。本轮未验证:Windows、Linux、workflow 在 GitHub Actions 里的实际行为,以及发布/打包链路。
f36538d
|
Processed the maintainer verification and the remaining review threads in Fixed before merge:
Verification:
Deferred under the PR's existing Stage 2 boundary and the repository's critical-only rule after repeated review rounds: a pnpm CVE/Dependabot lane, the pnpm lock integrity parser redesign, the stale |
|
Review round closed at head f36538d.\n\nFixed:\n- Merged current main at 96b14f2.\n- Pinned @types/node 20.19.1 in acp-bridge and regenerated both lockfiles; the independently reproduced pnpm-only TS2322 failure now passes.\n- Refreshed pnpm fast-uri 3.1.5 → 3.1.7 and qs 6.15.2 → 6.16.0.\n- Mirrored the npm react-markdown ^9.0.0 override, removed the duplicate pnpm 10.x graph, and added an override-parity regression test.\n- Qualified the worktree disk claim by copy-on-write support.\n- Changed the documented refresh to pnpm install --lockfile-only --no-frozen-lockfile so it does not run lifecycle/NOTICES generation.\n\nVerification:\n- Clean npm ci completed, including the full build and bundle.\n- npm run typecheck passed.\n- Focused package-script suite: 39 passed, 1 platform skip.\n- Frozen pnpm lock validation and both lockfile integrity checks passed.\n- Targeted ESLint and Prettier checks passed.\n- Independent exact-head reproduction confirmed the old pnpm Node-types failure and the vulnerable fast-uri/qs resolutions; its temporary pin made acp-bridge typecheck pass.\n\nDisclosure: a full build from the pnpm install-only layout on merged main still selects a newer hoisted esbuild (0.25.12 versus npm lock 0.25.6) and exceeds the export-runtime budget; the PR explicitly keeps build/test on the authoritative npm layout, which is green. No pnpm build claim is added here.\n\nDeferred under the round-7 convergence rule and maintainer recommendation: adding pnpm audit/Dependabot automation, lockfile-checker hardening, inert configuration cleanup, Corepack path/retry hardening, and the comment-only test wording cleanup. Each deferral is recorded in its resolved thread. |
qqqys
left a comment
There was a problem hiding this comment.
Critical-only review at head f36538d4817df4528052316fa23cb6a13021a1d1 (base main). Approving: every blocking finding from the previous rounds is fixed on this commit, and a full read of the hand-written surface turned up no Critical.
Historical blocking findings — verified against this head
| Finding | Verdict | Evidence |
|---|---|---|
R7-1 (round 7 Critical) — the new pnpm lockfile pinned [email protected], which carries four HIGH advisories fixed in 3.1.6, and [email protected] against npm's 6.16.0 |
Fixed | pnpm-lock.yaml now resolves [email protected] (:6377, :14212, :15991) and [email protected] (:8599, :10972, :18571), matching package-lock.json (node_modules/fast-uri → 3.1.7, node_modules/qs → 6.16.0). No 3.1.5 / 6.15.2 instance survives in the lock. |
| R1-1 — the Windows mock writes CRLF, so the multi-line log assertion was red on the Windows lane | Fixed | scripts/tests/package-scripts.test.js splits with /\r?\n/ at :352, :718, :769 and :881. |
R3-1 — findOnPath read env.PATH from a plain spread, which is Path on Windows, so Corepack was never found |
Fixed | scripts/setup-worktree.js:31-35 — pathValue() returns env.PATH off win32 and otherwise finds the key case-insensitively before falling back. |
P1 — the npx --yes pnpm@… fallback discarded the committed +sha512 package-manager integrity pin |
Fixed | The fallback is gone: setup-worktree.js:47-52 prints Corepack is required to verify the pinned pnpm package and process.exit(1) when Corepack is absent, and runPnpm has the single spawnSync(corepack, ['pnpm', ...args]) path. scripts/pnpm-package.js:7-19 rejects any packageManager that is not an exact [email protected] (optional +sha512.<128 hex>), and setup-worktree.js:19-21 calls it at module load, so a bad pin fails before any install. |
R5-1 — packages/vscode-ide-companion could not resolve @qwen-code/qwen-code-core under the hoisted linker |
Fixed and gated | .pnpmfile.mjs rewrites both names to workspace:* in pnpm's in-memory resolution, pnpm-workspace.yaml sets nodeLinker: 'hoisted' with linkWorkspacePackages: true, and the smoke workflow's Verify workspace links resolve step is exactly require.resolve('@qwen-code/qwen-code-core/package.json', { paths: ['packages/vscode-ide-companion'] }). Install (ubuntu-latest), Install (macos-latest) and Install (windows-latest) are all green on this head. |
The non-blocking parity items from the same rounds landed too: react-markdown: '^9.0.0' is now in the pnpm-workspace.yaml overrides with a new test pinning that map against package.json's, and AGENTS.md states the ≈1.2 GiB cost on filesystems without reflink plus the --lockfile-only --no-frozen-lockfile regeneration command.
Critical-only scan of the current diff — nothing blocking
I read every hand-written file this PR adds or changes: scripts/setup-worktree.js, scripts/pnpm-package.js, .pnpmfile.mjs, pnpm-workspace.yaml and .github/workflows/pnpm-worktree-smoke.yml, plus the patches to scripts/check-lockfile.js, generate-notices.js, AGENTS.md and the manifests.
- The new workflow asks for
permissions: contents: 'read'only, uses no secrets, cancels in progress solely forpull_request(so a post-merge witness cannot be erased), and its final step fails on anygit status --porcelainoutput — which is what makes "the bootstrap leaves no tracked changes" an enforced property rather than a claim. - The bootstrap spawns a fixed argv with no interpolated user input (
shell: trueonly on win32, wherecorepack.cmdrequires it), and its exit handling separateserror,signal(128 + signal number) andstatus. - The offline-first design escalates to
--prefer-offlineonly for a plain non-zero exit below 128; a signal or a hard error exits instead of silently retrying, so an interrupted install cannot masquerade as a cache miss. .pnpmfile.mjsmutates only pnpm's in-memoryreadPackageresult, so an internal dependency spelledfile:,*or an exact release version resolves asworkspace:*without the committed lock going stale on a release bump; npm's lockfile is untouched apart from the@types/nodedevDependency added topackages/acp-bridge.QWEN_SKIP_PREPAREandQWEN_SKIP_NOTICE_GENERATIONare set in the child env, which is what keeps the bootstrap from triggering the repository prepare build or rewriting the trackedNOTICES.txt.
Recorded, not gating: nothing asserts cross-lockfile version parity, so a future pnpm install could again resolve a package below npm's version — the shape R7-1 had. A missing test is not a Critical under this review's scope, and the defect itself is fixed at this head.
Not audited: the 20,603 generated lines of pnpm-lock.yaml beyond the fast-uri / qs check above, and the 460-line test addition beyond the assertions quoted.
CI
Green at this head: the three Install (…) pnpm smoke legs, Integration Tests (no-AK, No Sandbox), Desktop Shell (ubuntu-22.04 / windows-2022), Classify PR, the Java and real-daemon E2E lanes, and the triage/label/assign jobs. Still running when this review was posted: Lint & Static (ubuntu-latest, Node 22.x), Test (ubuntu-latest, Node 22.x) and review-pr — pending checks are not treated as a gate here, and nothing is red. The CHANGES_REQUESTED on the page is anchored at the previous head d894f363 (round 7) and is answered by the lock refresh above.
中文说明
在 head f36538d4 上执行 Critical-only 评审,结论为 Approve:历轮阻塞问题在该提交上全部确认修复,手写代码全量通读后未发现 Critical。
历史阻塞问题: R7-1(新 pnpm 锁文件钉住带 4 条 HIGH 公告的 [email protected],且 [email protected] 低于 npm 的 6.16.0)已修复——锁文件现为 [email protected]、[email protected],与 package-lock.json 一致,旧版本无残留;R1-1(Windows CRLF 导致断言在 Windows lane 变红)已改为 /\r?\n/ 分割;R3-1({...process.env} 展开后在 Windows 读不到 Path,Corepack 永远找不到)已由 pathValue() 的大小写不敏感查找修复;绕过完整性 pin 的 npx 回退已彻底删除,找不到 Corepack 直接 process.exit(1),且 pnpm-package.js 在模块加载时校验 packageManager 必须是精确版本;R5-1(vscode-ide-companion 在 hoisted linker 下解析不到 core)由 .pnpmfile.mjs 重写 + linkWorkspacePackages 修复,并被 smoke workflow 的解析步骤钉住,三系统 Install 全绿。同轮的 parity 建议项也一并落地(react-markdown override 与其镜像测试、AGENTS.md 的无 reflink 体积与再生成命令)。
本轮扫描: 已通读全部手写文件。新 workflow 只申请 contents: read、不用 secret、仅对 PR 取消并发(保留合入后见证),最后一步以任何 git status --porcelain 输出为失败,使「安装不改动受跟踪文件」成为被强制的不变量;bootstrap 使用固定 argv、无用户输入拼接(仅 win32 需要 shell),退出路径区分 error / signal(128+n) / status;offline 优先仅在退出码非零且小于 128 时升级到 --prefer-offline,信号或硬错误直接退出,不会把中断伪装成缓存未命中;.pnpmfile.mjs 只改 pnpm 内存中的解析结果,因此发布版本号变化不会让已提交的锁文件过期,npm 锁文件除 acp-bridge 新增 @types/node 外未变;子进程环境设置了 QWEN_SKIP_PREPARE 与 QWEN_SKIP_NOTICE_GENERATION,避免触发 prepare 构建或改写受跟踪的 NOTICES.txt。
仅记录、不作为卡点:目前没有跨锁文件版本一致性断言,未来一次 pnpm install 仍可能解析出低于 npm 的版本(即 R7-1 的形状);缺测试不属本次门禁的 Critical 范畴,且该缺陷本身已在本 head 修复。未审查:pnpm-lock.yaml 除 fast-uri/qs 之外的 20,603 行生成内容,以及 460 行测试新增中除引用断言外的部分。
CI: 本 head 上三条 Install pnpm smoke 腿、no-AK 集成、两端 Desktop Shell、Classify PR、Java 与 real-daemon E2E、triage/label/assign 均绿;Lint & Static、Test (ubuntu-latest, Node 22.x)、review-pr 评审时仍在运行——pending 不作为卡点,且无任何红。页面上的 CHANGES_REQUESTED 锚定在旧 head d894f363(第 7 轮),已由本次锁文件刷新回答。
chiga0
left a comment
There was a problem hiding this comment.
Tier: Scan + Standard (pnpm dev-tooling foundation; bulk is generated lockfile).
No blocking findings.
Approval blockers: none.
Deferred items from earlier rounds — disposition at current head
| Finding | Verdict at f36538d4 |
|---|---|
D6-2 — packageManager pin carries no corepack integrity hash; getPinnedPnpmPackage rejects +sha512 suffix |
Fixed — package.json:4 now carries the full +sha512.[128-hex] suffix; regex in pnpm-package.js accepts it; test 'pins pnpm with the corepack integrity hash' pins the format against the real package.json. |
D6-1 — core typecheck fails under pnpm layout (TS4111 @types/node version mismatch) |
Accepted follow-up — pnpm-workspace.yaml has no @types/node override; could not run typecheck without a local tree. Acceptable per documented scope ("pnpm layout is install-only for now", AGENTS.md). |
R5-1 — companion importer cannot resolve @qwen-code/qwen-code-core under pnpm layout |
Covered by smoke workflow — the Verify workspace links resolve step uses exactly this import path as the canary and would fail the workflow if unresolved. |
What I checked
Checked: CI workflow (pnpm-worktree-smoke.yml — permissions contents:read, SHA-pinned actions, no secrets, concurrency policy); setup-worktree.js (corepack resolution, offline→prefer-offline fallback, signal propagation, Windows PATH casing, env isolation); pnpm-package.js (version regex covers +sha512 suffix); pnpm-workspace.yaml (overrides, packageImportMethod: clone-or-copy + rationale, allowBuilds allowlist, minimumReleaseAgeExclude); check-lockfile.js (pnpm lockfile integrity walk — git/tarball exemptions); .pnpmfile.mjs (workspace rewrite hook); generate-notices.js and test (skip env var guard, non-skip path tested); scripts/tests/package-scripts.test.js (workspace membership sync, override mirrors, efficacy of worktree-bootstrap tests).
Cross-checked prior rounds: D6-2 confirmed fixed; D6-1 and R5-1 disposition noted above.
Not reviewed: pnpm-lock.yaml content beyond integrity check; Windows-specific CI output; macOS CI output.
Reviewed with AI assistance.



What this PR does
Adds an opt-in, frozen pnpm dependency bootstrap for additional Git worktrees while preserving every existing npm build, CI, versioning, packaging, and publishing path. Internal workspace dependencies are normalized only in pnpm's in-memory resolution so release version bumps cannot stale the pnpm lock during the dual-lock transition. A three-OS smoke workflow verifies that the bootstrap installs dependencies without changing tracked files.
Why it's needed
Each npm-backed worktree currently materializes roughly 1.44 GiB of dependencies and triggers the repository prepare build unless callers know the skip environment variable. The measured warm-store pnpm path materialized about 99 MiB, a 93.3% reduction, and completed dependency installation in about 22 seconds versus 27 seconds for warm-cache npm. This first stage makes that saving available independently, so later pnpm build/CI and release migrations can be reviewed and landed separately.
Reviewer Test Plan
How to verify
Create a fresh worktree, run the opt-in worktree bootstrap, and confirm that the frozen install succeeds and leaves no tracked changes. Confirm that existing npm build and release commands and the npm lockfile are unchanged. Change an internal channel dependency version locally and verify that pnpm's frozen lock check still passes without regenerating the lockfile.
Evidence (Before & After)
N/A — developer tooling only. A detailed benchmark and test report is posted in the PR conversation.
Tested on
Environment (optional)
macOS APFS, Node.js 22, pnpm 11.24.0. Local registry fallback was also observed; the registry subsequently timed out while downloading optional binaries for unrelated platforms, so the committed three-OS workflow is the authoritative clean-install gate.
Risk & Scope
Linked Issues
Part of #10444
中文说明
本 PR 做了什么
新增一个可选的、冻结锁文件的 pnpm worktree 依赖安装入口,同时保持现有 npm 构建、CI、版本管理、打包和发布链路不变。在双锁文件过渡期,内部 workspace 依赖只在 pnpm 的内存解析阶段被标准化,因此发布版本号变化不会使 pnpm 锁文件过期。新增三操作系统 smoke workflow,验证安装完成后不会改动受跟踪文件。
为什么需要
目前每个 npm worktree 都会额外落盘约 1.44 GiB 依赖,而且如果调用方不知道跳过环境变量,还会触发仓库 prepare 构建。实测 pnpm warm-store 路径约占 99 MiB,空间下降 93.3%,依赖安装约 22 秒,而 npm warm-cache 约 27 秒。第一阶段先独立提供这部分收益,后续 pnpm 构建/CI 和发布迁移可以分别评审、分别合入。
Reviewer Test Plan
如何验证
创建新 worktree,运行可选的 worktree bootstrap,确认冻结安装成功且没有受跟踪文件变化。确认现有 npm 构建、发布命令和 npm 锁文件均未变化。临时修改内部 channel 依赖版本,确认不重新生成锁文件时 pnpm frozen lock 检查仍能通过。
前后证据
N/A——仅开发工具改动。详细 benchmark 与测试报告已发布在 PR 评论区。
测试平台
环境
macOS APFS、Node.js 22、pnpm 11.24.0。本地也验证了 registry fallback 会被触发;随后 registry 在下载其他平台可选二进制包时超时,因此提交中的三系统 workflow 是权威的 clean-install gate。
风险与范围
关联 Issue
属于 #10444 的第一阶段。