Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
8aec13a
ci(pnpm): install dependencies with pnpm in CI and release
yiliang114 Sep 14, 2026
8e6d195
chore(vscode): regenerate NOTICES from the pnpm-installed tree
yiliang114 Sep 14, 2026
b5bdc92
fix(pnpm): resolve undeclared zod peers to the repo's zod, as npm does
yiliang114 Sep 14, 2026
584da9c
fix(pnpm): keep npm's root @types/node so root-level typechecks match
yiliang114 Sep 14, 2026
04610aa
ci(pnpm): install with pnpm in the remaining workflows
yiliang114 Sep 14, 2026
461d3a2
fix(pnpm): link the workspace packages that root-level code imports
yiliang114 Sep 14, 2026
13634af
ci(pnpm): move triage, the store producer and the root audit to pnpm
yiliang114 Sep 14, 2026
1402939
Merge remote-tracking branch 'origin/main' into feat/pnpm-ci-release-…
yiliang114 Sep 14, 2026
44a8a4c
feat(review): install pnpm repos in qwen review's build/test
yiliang114 Sep 14, 2026
9bccfc9
ci(pnpm): retire the root package-lock.json
yiliang114 Sep 14, 2026
47c87df
Merge remote-tracking branch 'origin/main' into feat/pnpm-ci-release-…
yiliang114 Sep 15, 2026
752b6a6
Merge remote-tracking branch 'origin/main' into feat/pnpm-ci-release-…
yiliang114 Sep 15, 2026
8a7d672
Merge remote-tracking branch 'origin/main' into feat/pnpm-ci-release-…
yiliang114 Sep 15, 2026
5ff946c
fix(standalone): read the node-pty versions from the pnpm lockfile
yiliang114 Sep 15, 2026
49cc24f
Merge remote-tracking branch 'origin/main' into feat/pnpm-ci-release-…
yiliang114 Sep 15, 2026
74cbda7
Merge origin/main into feat/pnpm-ci-release-install
yiliang114 Sep 15, 2026
d228113
Merge origin/main into feat/pnpm-ci-release-install
yiliang114 Sep 17, 2026
0d690d6
chore(vscode-ide-companion): regenerate NOTICES.txt after the lockfil…
yiliang114 Sep 17, 2026
5b81607
Merge remote-tracking branch 'origin/main' into wip-11859
yiliang114 Sep 17, 2026
5ee5ce9
chore(vscode-ide-companion): regenerate NOTICES.txt from the pnpm dep…
yiliang114 Sep 17, 2026
15dc732
Merge remote-tracking branch 'origin/main' into wip-11859
yiliang114 Sep 18, 2026
2a98f36
Merge remote-tracking branch 'origin/main' into wip-11859
yiliang114 Sep 18, 2026
4b365ae
fix(ci): drop the npm cache from the daily dependency audit
yiliang114 Sep 18, 2026
5a57e23
chore(ci): stop banking main-side drift in the workflow size ratchet
yiliang114 Sep 18, 2026
91ea2c7
Merge remote-tracking branch 'origin/main' into codex/pr-11859-releas…
yiliang114 Sep 18, 2026
0888f5d
fix(release): initialize standalone state before main
yiliang114 Sep 18, 2026
fcee577
test(browser-use): read Playwright pin from pnpm lock
yiliang114 Sep 18, 2026
5f4b8d8
Merge branch 'main' into feat/pnpm-ci-release-install
yiliang114 Sep 18, 2026
9861c49
fix(ci): address pnpm migration review blockers
yiliang114 Sep 19, 2026
88f0030
Merge branch 'main' into feat/pnpm-ci-release-install
yiliang114 Sep 19, 2026
51aa6fc
fix(desktop): stop reading the retired root lockfile
yiliang114 Sep 19, 2026
166bd8e
Merge remote-tracking branch 'origin/main' into codex/pr-11859-deskto…
yiliang114 Sep 19, 2026
7afb34b
Merge remote-tracking branch 'origin/main' into feat/pnpm-ci-release-…
yiliang114 Sep 19, 2026
34ab9f2
Merge remote-tracking branch 'origin/main' into codex/pr-11859-deskto…
yiliang114 Sep 19, 2026
832465e
Merge origin/main into feat/pnpm-ci-release-install
yiliang114 Sep 20, 2026
8905cb5
Merge remote-tracking branch 'origin/main' into feat/pnpm-ci-release-…
yiliang114 Sep 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/actions/pnpm-store-cache/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
name: 'pnpm store cache'
description: >-
Restore and save the pnpm content-addressable store on hosted runners,
keyed on pnpm-lock.yaml. Self-hosted runners keep their store on local disk
between jobs, so they do not need this.
runs:
using: 'composite'
steps:
- name: 'Locate the pnpm store'
id: 'store'
shell: 'bash'
run: 'echo "path=$(corepack pnpm store path --silent)" >> "${GITHUB_OUTPUT}"'
Comment thread
yiliang114 marked this conversation as resolved.

- name: 'Cache the pnpm store'
uses: 'actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830' # v4.3.0
with:
path: '${{ steps.store.outputs.path }}'
key: "pnpm-store-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}"
restore-keys: 'pnpm-store-${{ runner.os }}-'
13 changes: 8 additions & 5 deletions .github/scripts/ci-disk-pressure.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,10 @@ function lintStep(name) {
}

describe('ci.yml disk-pressure evidence', () => {
it('starts sampling before npm ci and preserves those samples for upload', () => {
it('starts sampling before the install and preserves those samples for upload', () => {
const install = step('Install dependencies').run;
const npmCi = install.indexOf('npm ci');
const npmCi = install.indexOf('pnpm install');
assert.ok(npmCi !== -1, 'the install step must run pnpm install');

assert.match(
install,
Expand Down Expand Up @@ -241,9 +242,11 @@ describe('ci.yml disk-pressure evidence', () => {

it('keeps install failure status while writing the pre-install sample', () => {
const root = mkdtempSync(join(tmpdir(), 'ci-disk-pressure-'));
const npm = join(root, 'npm');
writeFileSync(npm, '#!/usr/bin/env bash\nexit 42\n');
chmodSync(npm, 0o755);
// The install step runs `corepack pnpm install`; stub corepack so the
// failing exit comes from the install command itself.
const corepack = join(root, 'corepack');
writeFileSync(corepack, '#!/usr/bin/env bash\nexit 42\n');
chmodSync(corepack, 0o755);

try {
const result = spawnSync(
Expand Down
2 changes: 1 addition & 1 deletion .github/scripts/ci/classify-platform-sensitivity.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const RUNNER_CONFIG = /(?:^|\/)vitest(?:\.[^/]*)?\.config\.[cm]?[jt]s$/i;

// The dependency and script manifests: a changed `test:ci`, a native module, or
// an optional per-platform dependency changes what each lane executes.
const MANIFEST = new Set(['package.json', 'package-lock.json']);
const MANIFEST = new Set(['package.json', 'pnpm-lock.yaml']);
Comment thread
yiliang114 marked this conversation as resolved.

// Source subtrees whose subject IS the host.
//
Expand Down
2 changes: 1 addition & 1 deletion .github/scripts/ci/classify-platform-sensitivity.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ test('the runner configuration decides which lane runs what', () => {

test('the manifests change what each lane executes', () => {
assert.equal(classifyChangedFiles(['package.json']), PLATFORM_SENSITIVE);
assert.equal(classifyChangedFiles(['package-lock.json']), PLATFORM_SENSITIVE);
assert.equal(classifyChangedFiles(['pnpm-lock.yaml']), PLATFORM_SENSITIVE);
// A workspace manifest is not the root one; it reaches the lanes through
// the subsystem rules or not at all.
assert.equal(
Expand Down
55 changes: 29 additions & 26 deletions .github/scripts/qwen-triage-workflow.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const cacheProducerPath = join(
dirname(fileURLToPath(import.meta.url)),
'..',
'workflows',
'npm-cache.yml',
'pnpm-store.yml',
);
const cacheProducerDoc = parse(readFileSync(cacheProducerPath, 'utf8'));
const prWorkflowPath = join(
Expand Down Expand Up @@ -1139,16 +1139,19 @@ describe('qwen-triage: Stage 1e revert-pattern signals', () => {
});
});

describe('qwen-triage: npm cache restore-only invariant', () => {
describe('qwen-triage: pnpm store restore-only invariant', () => {
for (const [jobName, jobDef] of [
['verify', verifyJob],
['tmux-testing', tmuxJob],
]) {
it(`${jobName}: uses actions/cache/restore with no save path`, () => {
const cacheStep = jobDef.steps.find(
(s) => s.name === 'Restore npm cache',
(s) => s.name === 'Restore pnpm store',
);
assert.ok(
cacheStep,
`'Restore pnpm store' step must exist in ${jobName}`,
);
assert.ok(cacheStep, `'Restore npm cache' step must exist in ${jobName}`);
assert.match(
cacheStep.uses,
/^actions\/cache\/restore@/,
Expand All @@ -1165,9 +1168,9 @@ describe('qwen-triage: npm cache restore-only invariant', () => {
}
});

it(`${jobName}: npm ci --cache matches the restored directory`, () => {
it(`${jobName}: pnpm install --store-dir matches the restored directory`, () => {
const cacheStep = jobDef.steps.find(
(s) => s.name === 'Restore npm cache',
(s) => s.name === 'Restore pnpm store',
);
const prepareStep = jobDef.steps.find(
(s) => s.name === 'Install and build PR app',
Expand All @@ -1182,25 +1185,25 @@ describe('qwen-triage: npm cache restore-only invariant', () => {
);
assert.ok(dir, 'cache path must resolve to a directory name');
assert.ok(
prepareStep.run.includes(`--cache "$RUNNER_TEMP/${dir}"`),
`npm ci must use --cache "$RUNNER_TEMP/${dir}"`,
prepareStep.run.includes(`--store-dir "$RUNNER_TEMP/${dir}"`),
`pnpm install must use --store-dir "$RUNNER_TEMP/${dir}"`,
);
});

it(`${jobName}: clears stale npm cache before restore`, () => {
it(`${jobName}: clears stale pnpm store before restore`, () => {
const clearIdx = jobDef.steps.findIndex(
(s) => s.name === 'Clear stale npm cache',
(s) => s.name === 'Clear stale pnpm store',
);
const restoreIdx = jobDef.steps.findIndex(
(s) => s.name === 'Restore npm cache',
(s) => s.name === 'Restore pnpm store',
);
assert.ok(
clearIdx !== -1,
`'Clear stale npm cache' step must exist in ${jobName}`,
`'Clear stale pnpm store' step must exist in ${jobName}`,
);
assert.ok(
restoreIdx !== -1,
`'Restore npm cache' step must exist in ${jobName}`,
`'Restore pnpm store' step must exist in ${jobName}`,
);
assert.ok(
clearIdx < restoreIdx,
Expand All @@ -1212,7 +1215,7 @@ describe('qwen-triage: npm cache restore-only invariant', () => {
'clear step must rm -rf the cache directory',
);
const cacheStep = jobDef.steps.find(
(s) => s.name === 'Restore npm cache',
(s) => s.name === 'Restore pnpm store',
);
const dir = cacheStep.with.path.replace(
/^\$\{\{\s*runner\.temp\s*\}\}\//,
Expand All @@ -1226,34 +1229,34 @@ describe('qwen-triage: npm cache restore-only invariant', () => {

it(`${jobName}: reports the cache hit so a permanent miss is visible`, () => {
const cacheStep = jobDef.steps.find(
(s) => s.name === 'Restore npm cache',
(s) => s.name === 'Restore pnpm store',
);
assert.equal(
cacheStep.id,
'npm-cache',
'pnpm-store',
'restore step needs an id so its cache-hit output is readable',
);
const reportStep = jobDef.steps.find(
(s) => s.name === 'Report npm cache hit',
(s) => s.name === 'Report pnpm store hit',
);
assert.ok(reportStep, "'Report npm cache hit' step must exist");
assert.ok(reportStep, "'Report pnpm store hit' step must exist");
assert.match(
reportStep.run,
/steps\.npm-cache\.outputs\.cache-hit/,
/steps\.pnpm-store\.outputs\.cache-hit/,
'report step must surface the cache-hit output',
);
});
}
});

describe('qwen-triage: npm cache producer workflow', () => {
describe('qwen-triage: pnpm store producer workflow', () => {
const saveJob = cacheProducerDoc.jobs.save;

it('triggers on push to main only', () => {
const push = cacheProducerDoc.on.push ?? cacheProducerDoc[true]?.push;
assert.ok(push, 'must have a push trigger');
assert.deepEqual(push.branches, ['main']);
assert.deepEqual(push.paths, ['package-lock.json']);
assert.deepEqual(push.paths, ['pnpm-lock.yaml']);
});

it('saves with the same key and path the triage lanes restore', () => {
Expand All @@ -1266,7 +1269,7 @@ describe('qwen-triage: npm cache producer workflow', () => {
['tmux-testing', tmuxJob],
]) {
const restoreStep = jobDef.steps.find(
(s) => s.name === 'Restore npm cache',
(s) => s.name === 'Restore pnpm store',
);
assert.equal(
saveStep.with.path,
Expand All @@ -1292,12 +1295,12 @@ describe('qwen-triage: npm cache producer workflow', () => {
);
assert.ok(dir, 'save path must resolve to a directory name');
const populateStep = saveJob.steps.find(
(s) => s.name === 'Populate npm cache',
(s) => s.name === 'Populate pnpm store',
);
assert.ok(populateStep, "'Populate npm cache' step must exist");
assert.ok(populateStep, "'Populate pnpm store' step must exist");
assert.ok(
populateStep.run.includes(`--cache "$RUNNER_TEMP/${dir}"`),
`populate step must fill the saved cache directory (--cache "$RUNNER_TEMP/${dir}")`,
populateStep.run.includes(`--store-dir "$RUNNER_TEMP/${dir}"`),
`populate step must fill the saved store directory (--store-dir "$RUNNER_TEMP/${dir}")`,
);
});

Expand Down
2 changes: 1 addition & 1 deletion .github/scripts/run-autofix-review-verification.sh
Original file line number Diff line number Diff line change
Expand Up @@ -731,7 +731,7 @@ sensitive_class_of() {
scripts/tests/*) ;;
scripts/*) echo 'repo-scripts' ;;
.npmrc | .nvmrc | */.npmrc | */.nvmrc) echo 'toolchain-config' ;;
package-lock.json | npm-shrinkwrap.json | */package-lock.json | */npm-shrinkwrap.json | patches/*) echo 'supply-chain' ;;
package-lock.json | npm-shrinkwrap.json | */package-lock.json | */npm-shrinkwrap.json | pnpm-lock.yaml | pnpm-workspace.yaml | .pnpmfile.mjs | patches/*) echo 'supply-chain' ;;
.gitattributes | */.gitattributes) echo 'measurement-config' ;;
*) case "${f##*/}" in
eslint.config.* | eslint.legacy-filenames.mjs | eslint.legacy-core-barrel-imports.mjs | vitest.config.* | tsconfig.json | tsconfig.*.json)
Expand Down
2 changes: 1 addition & 1 deletion .github/scripts/run-release-step.sh
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ case "${step}" in

push-release-branch)
release_branch_name="${BRANCH_NAME:?}"
git add package.json package-lock.json packages/*/package.json packages/channels/*/package.json integrations/*/package.json integrations/*/qwen-extension.json
git add package.json pnpm-lock.yaml packages/*/package.json packages/channels/*/package.json integrations/*/package.json integrations/*/qwen-extension.json

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified at head 34ab9f2349 by reading only — no build or test executed.

Confirmed. run-release-step.sh:126 (case push-release-branch, opened at :124) is git add package.json pnpm-lock.yaml packages/*/package.json …, under set -eo pipefail at :8. At base 94946f1b the same line named package-lock.json, so this PR is what makes the pathspec unconditional on a file that did not exist in the repo until 3ecfaffdf8 (2026-09-08, #10449) — I confirmed that add date with git log --diff-filter=A -- pnpm-lock.yaml rather than taking it on trust. Any release ref before that date has no pnpm-lock.yaml; git add fails wholesale on an unmatched pathspec, set -eo pipefail aborts the step, and Conditionally push release branch (release.yml:668-677) dies — no release/<tag> branch, so the version bump is never committed or merged back.

The script really does run against the operator-chosen tree rather than the workflow's own: release.yml:158 checks out ref: '${{ github.event.inputs.ref || github.sha }}', while :165/:172 pull the scripts separately at github.workflow_sha into .release-workflow, and the publish job re-checks-out needs.prepare.outputs.release_sha at :632. New scripts + old tree is the designed combination, not an accident.

One correction to the ordering, which matters for whoever fixes this: :126 is the second blocker on that path, not the first. The same job's Install Dependencies at release.yml:647-651 runs corepack pnpm install --frozen-lockfile --ignore-scripts --prefer-offline --reporter=append-only and dies on a tree with no pnpm lockfile before execution ever reaches the git add. Fixing only the install step moves the failure here; both have to land together.

This is the root cause @chiga0 already adjudicated as blocker B3 at 7afb34b2 — "Historical-ref jobs install with pnpm into a tree committed with npm; --frozen-lockfile fails on pre-PR refs" — under the verdict "Blockers confirmed. Do not approve." @qqqys carried the same class as R1-1/R1-3 for finalize-release.yml and sync-release-to-oss.yml, reported as not-verified-within-budget. I am not re-arguing either ruling; this thread is the release.yml instance of B3 and it stands or falls with it.

Two constraints on the fix, both verified: scripts/tests/release-workflow.test.js:2273 pins the current command string verbatim, so a conditional git add must update that assertion in the same change; and the conditional needs a trailing true, because a false [ -f … ] && git add … as the last command in the step exits 1 under set -eo pipefail.

No patrol code lands this round: size-fused at +2016/-34725, head 34ab9f2349 is owner-authored. Gate: release-path data loss — a failed push-release-branch silently drops the version-bump commit and the merge-back. There is also a prior question that only a maintainer can answer: whether historical-ref releases must keep working at all. If the answer is "no, releases only ever run from post-migration refs", that decision should be recorded on this PR instead of left implicit, because it retires B3/R1-1/R1-3 in one stroke.

Leaving this thread unresolved.

if git diff --staged --quiet; then
echo "No version changes to commit"
else
Expand Down
47 changes: 23 additions & 24 deletions .github/workflows/.size-baseline
Original file line number Diff line number Diff line change
Expand Up @@ -17,56 +17,55 @@
3480 audio-capture-prebuilds.yml
9023 auto-minimize-spam.yml
9256 build-and-publish-image.yml
49610 cd-cua-driver.yml
2076 cd-mobile-mcp.yml
137297 ci.yml
50773 cd-cua-driver.yml
2222 cd-mobile-mcp.yml
137804 ci.yml
1482 codeql.yml
9389 comment-attachment-guard.yml
1634 desktop-packaging-check.yml
35684 desktop-release.yml
38401 desktop-release.yml
2038 docs-page-action.yml
10005 dsw-swe-verified-release.yml
32363 e2e.yml
11394 finalize-release.yml
33227 e2e.yml
11634 finalize-release.yml
16647 live-host-release.yml
5950 live-host.yml
1343 mobile-shell.yml
7642 main-ci-failure-issue.yml
1686 npm-cache.yml
2709 pnpm-lock-freshness.yml
2277 pnpm-store.yml
2489 pnpm-worktree-smoke.yml
7299 pr-force-push-reminder.yml
6495 pr-self-report-label.yml
9646 qwen-autofix-fork-bridge.yml
5942 qwen-autofix-fork-signal.yml
469165 qwen-autofix.yml
466739 qwen-autofix.yml
7061 qwen-ci-flaky-rerun.yml
265415 qwen-code-pr-review.yml
265236 qwen-code-pr-review.yml
107075 qwen-fleet-shepherd.yml
22680 qwen-issue-followup-bot.yml
5760 qwen-pr-safety-precheck.yml
2518 qwen-review-runner-schedule.yml
27648 qwen-triage-finalize.yml
350381 qwen-triage.yml
9657 release-sdk-java.yml
22037 release-sdk-python.yml
19094 release-sdk.yml
14546 release-vscode-companion.yml
34861 release.yml
43717 repo-hygiene.yml
354514 qwen-triage.yml
9701 release-sdk-java.yml
22345 release-sdk-python.yml
20008 release-sdk.yml
16655 release-vscode-companion.yml
35955 release.yml
44285 repo-hygiene.yml
1079 scorecard-monthly.yml
10691 sdk-java.yml
12011 sdk-java.yml
3886 sdk-python.yml
3197 security-checks.yml
5595 security-checks.yml
6777 serve-ab-publish.yml
17013 serve-ab.yml
20096 serve-ab.yml
2641 stale.yml
11328 sync-desktop-to-oss.yml
10018 sync-live-host-to-oss.yml
10988 sync-release-to-oss.yml
2508 tui-parity.yml
11206 sync-release-to-oss.yml
2676 tui-parity.yml
7187 update-ecs-runner-qwen.yml
2307 web-shell-visuals-cleanup.yml
20047 web-shell-visuals-publish.yml
16384 web-shell-visuals.yml
4712 windows-runner-smoke.yml
17127 web-shell-visuals.yml
5155 windows-runner-smoke.yml
4 changes: 1 addition & 3 deletions .github/workflows/cd-cua-driver.yml
Original file line number Diff line number Diff line change
Expand Up @@ -699,14 +699,12 @@ jobs:
- uses: 'actions/setup-node@v4'
with:
node-version: '22'
cache: 'npm'
cache-dependency-path: 'package-lock.json'
registry-url: 'https://registry.npmjs.org'
scope: '@qwen-code'
- name: 'Install npm 11'
run: 'npm install --global [email protected]'
- name: 'Install workspace dependencies'
run: 'npm ci --ignore-scripts --no-audit --no-fund --progress=false'
run: 'corepack pnpm install --frozen-lockfile --ignore-scripts --prefer-offline --reporter=append-only'
- name: 'Test standalone Node REPL package'
working-directory: 'packages/node-repl'
run: |
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/cd-mobile-mcp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,7 @@ jobs:
- uses: 'actions/setup-node@v5'
with:
node-version-file: '.nvmrc'
cache: 'npm'
cache-dependency-path: 'package-lock.json'
package-manager-cache: false
registry-url: 'https://registry.npmjs.org'
scope: '@qwen-code'

Expand All @@ -47,13 +46,14 @@ jobs:
fi
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
# mobile-mcp is a root workspace package, so it installs from the root
# pnpm lockfile, as `npm ci` in this directory used the root npm one.
- name: 'Install dependencies'
working-directory: 'packages/mobile-mcp'
run: 'npm ci --ignore-scripts'
run: 'corepack pnpm install --frozen-lockfile --ignore-scripts --reporter=append-only'

- name: 'Set version'
working-directory: 'packages/mobile-mcp'
run: 'npm version "${{ steps.version.outputs.version }}" --no-git-tag-version'
run: 'npm version "${{ steps.version.outputs.version }}" --no-git-tag-version --no-workspaces-update'

- name: 'Build'
working-directory: 'packages/mobile-mcp'
Expand Down
Loading
Loading