Commit Graph
50 Commits
Author SHA1 Message Date
易良andyiliang114 708ba347d3 feat(ci): add disposable external PR runners (#16513)
* feat(ci): add disposable external PR runners

* test(ci): align platform lanes with disposable runner routing

---------

Co-authored-by: yiliang114 <[email protected]>
2026-11-07 14:24:33 +00:00
易良andQwen-Coder 8660f75430 fix(ci): refresh host assets on older PR checkouts (#16476)
* fix(ci): refresh host assets on older PR checkouts

Co-authored-by: Qwen-Coder <[email protected]>

* style(ci): format host asset regression cases

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-07 01:42:26 +08:00
7cb46d9fed feat(hooks): add FileChanged notifications for local sessions (#13969)
* feat(hooks): add FileChanged notifications for local sessions

* fix(ci): preserve shell gate coverage and trim transcript data

* fix(ci): flush debug prompts and isolate git fixtures

* fix(hooks): respect ACP watcher ownership and skill registration

* test: stabilize hosted recovery and failed spawn fixtures

* fix(ci): budget the complete hosted integration fork

* fix(runtime-broker): accept MariaDB CSI guard metadata

* test: align schema baseline and hook dispatch waits

* fix(ci): allow hosted shared fork to finish all classes

* fix(hooks): preserve file watching across errors and cwd changes

* fix(ci): isolate reference archive extraction per run

* test(ci): keep reference archive checks dependency-free

* fix(hooks): constrain recursive watches and repair CI

* test(web-shell): synchronize verified connection return

* fix(ci): correct runtime broker callback indentation

* test(web-shell): match the managed workspace registration label

Keep the remote workspace registration smoke checks in sync with the service label added by unified workspace navigation. Preserve exact text matching and all registration and replay assertions.

* fix(ci): reduce standalone runtime fixture overhead

* fix(hooks): ignore implicit atomic-write temporary files

* test(managed-agent): recover initial node port conflicts

* fix(hooks): observe parents before discovery anchors disappear

* test(managed-agent): await stream cleanup before recovery teardown

* test(managed-agent): align hosted shell fixture capabilities

* test(managed-agent): let OS choose initial harness port

* fix(ci): restore runner-local Java cache permissions

* test(runtime-broker): use native execution profile paths

* test(managed-agent): bind valid workspace upload probes

* test(managed-agent): reuse the upload probe digest fixture

* test(managed-agent): bind hook fault listeners to available ports

* test(managed-agent): align the native profile path fixture

* test(managed-agent): Wait for projection refusal retries

* ci: Prepare daemon Java cache before Node preflight

* test(managed-agent): check channel model calls after delivery

* fix(hooks): observe explicitly registered symlink targets

* test(managed-agent): separate consumption from relay snapshots

* test(managed-agent): target tamper injection by resource key

* test(managed-agent): exhaust attestation EOF retry in fault gates

* fix(ci): bound RocketMQ release downloads

* test(tui): await the restored assistant before capturing history

* fix(ci): use a checksum-pinned RocketMQ mirror

* fix(ci): provision missing browser test fonts

* fix(ci): quote the font probe environment

* fix(ci): prepare browser fonts without elevation

* test(core): compare complete prune reports without ordering

* fix(ci): repair only runner-local Java tool caches

* test(managed-agent): qualify fixture JDBC templates

* fix: isolate reconnect hooks and cancellation test fixtures

Skip chat initialization so MCP reconnect cannot re-enable file hooks, and keep cancellation fixtures isolated from the background worktree scanner.

* fix(ci): tolerate a missing Java cache helper on resumed releases

Resuming an existing release checks out a tag that may predate the helper, which failed all five Java jobs before setup-java ran.

* fix: finish the main merge for harness deadlines and workflow size

Use the renamed settle-timeout accessor in the merged hosted-harness tests,
and record sdk-java.yml's new size after the resumed-release cache guard.

* style(managed-agent): restore import grouping in merged verification tests

* test(managed-agent): require consumed child result mirrors

* test(web-shell): center repository settings capture

---------

Co-authored-by: yiliang114 <[email protected]>
Co-authored-by: yungsen.hys <[email protected]>
Co-authored-by: Shaojin Wen <[email protected]>
2026-11-06 21:43:01 +08:00
chencheng (云谦) 1b1f4455d9 ci: build the PR tree once and shorten the required path (#16450)
* ci: build the PR tree once and shorten the required path

The no-AK integration gate, the four web-shell smoke shards and Lint & Static each rebuilt the tree test_build already uploads, and the build cache rarely helped: most runs logged "29 of 29 workspaces changed" because the arch-keyed cache was split across the mixed x64/ARM64 pool and a prefix restore returned the newest entry instead of the base commit's.

- test_build restores the build its base commit saved (ci-build-cache.mjs key --at), drops runner.arch from the key, and runs the serve fast-path closure and core subpath checks against the built tree.
- The no-AK integration gate unpacks that tree and runs as three shards; integration_no_ak_result keeps the required check name.
- The web-shell smoke unpacks the same tree instead of building; consumers recopy the opentui native assets for their host (host-assets).
- The standalone runtime regression moves from unit shard 1 to shard 4.
- A java_only profile skips the TypeScript lanes for changes confined to the Java packages; Lint & Static runs the scripts/tests suites that name them.
- Drafts skip the integration gate and the browser smoke; ready_for_review starts the full run.

* ci: check the settings schema in test_build, which has the core dist

Lint & Static only had core's dist because the serve fast-path check built
it; with that check moved, Generate settings schema failed to import it.

* ci: keep the standalone regression on unit shard 1

Moving it did not shorten shard 1: the regression runs in the side lane,
in parallel with the CLI and core suites, and shard 1's time is its CLI
slice (576 s against 147 s on shard 4 for the same 181 files). That
imbalance is left to duration-based sharding.
2026-11-06 03:30:11 -08:00
易良andQwen-Coder 1a20792cfa ci: route the no-AK integration gate to ECS for every pull request (#16132)
* ci: route the no-AK integration gate to ECS for every pull request

Same trust class as lint_and_static (2026-11-02 extension of the
2026-10-31 decision): the integration step executes PR code, but the
lane carries zero secrets by construction — every model key is emptied
at the step and the job's only token is the read-only checkout grant.
The gate's step-level runner.temp HOME becomes run-unique and is
cleared before use (the R2-1 lesson: RUNNER_TEMP survives across jobs
on this pool). test and integration_cli keep the association-gated
ubuntu_runner. Mutation-verified: runs-on revert, fixed HOME, and a
real key in the PR-code step each red the routing battery.

* fix(ci): isolate no-AK HOME from the first step; value-pinned zero-secrets

Round-2 review findings: (1) Critical — the gate-scoped HOME missed the
lane's actual fork-code start: checkout's local action and the install
(lifecycle scripts plus scripts/prepare.js, un-skipped) ran under the
runner's real shared home with a shared COREPACK_HOME — the exact
cross-job poisoning channel the lint acceptance closed. Mirror lint:
first-step GITHUB_ENV run-unique HOME + COREPACK_HOME inside it +
shared verified XDG cache + always() reclamation. (2) Critical — the
zero-secrets guard was name-based and missed odd-named secrets; enforce
by VALUE (every non-path env key empty, no secrets.* anywhere in the
gate). (3) runs-on literal pin tightened (timeout line also mentions
lint_runner). Docs: EN single-lane phrasing, stale merge-queue claim,
2026-11-02 paragraph rewritten for the first-step shape. Six mutations
red (first-step removal, shared corepack, odd-name secret, no cleanup,
runs-on revert, fixed HOME).

* docs(ci): correct the enforcement-order claim and stale trust-basis note

Round-2 review: the doc claimed the routing battery runs before
dependency install — false (HELPER_TESTS imports yaml, runs after
install, and not at all on github_ci_only PRs' own runs); and the
ci.yml trust-basis comment still described the round-1 gate-scoped
HOME shape. State the actual timing and detection latency, and the
shipped first-step shape.

* fix(ci): scrub ANTHROPIC_AUTH_TOKEN at the no-AK gate; honest cache naming

Round-3 review: the Anthropic SDK's auth resolver back-fills
ANTHROPIC_AUTH_TOKEN from env, so a runner-account residue would survive
into fork PR code on the pool — add it to the gate's scrub list (pinned
by both suites). The 'Configure persistent npm cache' step name promised
persistence the per-run HOME removes; rename it and record the accepted
cold-install cost (shared pnpm store stays a follow-up).

* fix(ci): scrub ANTHROPIC_AUTH_TOKEN at the no-AK gate; honest cache naming

Round-3 review: the Anthropic SDK's auth resolver back-fills
ANTHROPIC_AUTH_TOKEN from env, so a runner-account residue would survive
into fork PR code on the pool — add it to the gate's scrub list. The
value whitelist only iterates present keys, so the deletion mutation
escaped it; pin the key's presence-and-empty in the battery and the
28-key literal list. The 'Configure persistent npm cache' step name
promised persistence the per-run HOME removes; rename and record the
accepted cold-install cost (shared pnpm store stays a follow-up).

* test(ci): pin the no-AK grant, HOME template and reclamation path

Three assertions in the no-AK routing battery proved less than they read:

- the job's `permissions` was never inspected, so the read-only-checkout
  grant that justifies routing untrusted fork code onto the pool could be
  widened without a red test;
- `create.includes('HOME=')` is satisfied by `COREPACK_HOME=` and
  `XDG_CACHE_HOME=`, so deleting the real HOME/USERPROFILE publication kept
  the test green;
- the cleanup only had to contain the `qwen-no-ak-home-` prefix, so a path
  that dropped `-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}` would still pass
  while removing a directory nothing used.

Pin the grant by value, the publication by its printf template, and the
reclamation by the exact run-unique path the create step builds.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmw48g5gafx

* test(ci): pin the realized HOME values in the shared ungated lane loop

R2-1: assert the create `home=` line, the gate QWEN_HOME and the cleanup
command by value, and require the exact set of `$GITHUB_ENV` writers. An
`echo` stand-in for the cleanup, a `-v2` create path and an appended second
HOME writer all kept the battery green under containment checks.

R1-2 / R1-1: hoist those pins into the shared `ungated` loop so
`lint_and_static` gets them too. Its create step was pinned by
`includes('HOME=')` (satisfied by `COREPACK_HOME=`) and its cleanup by
`includes('qwen-')`, so a dropped `HOME=%s` or a retargeted `rm -rf` survived.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmw4h0sl6g9

* test(ci): red on later republishes of the isolated HOME keys

R3-1: the GITHUB_ENV equality guard read step 0 only, so a later step that
republished HOME reverted the run-unique home by last-write-wins while every
assertion stayed green — the isolation would silently stop applying to the
steps that run PR code. Scan the remaining steps for isolated-key
$GITHUB_ENV writers and for step-level HOME/USERPROFILE/COREPACK_HOME/
XDG_CACHE_HOME overrides, leaving the npm cache step's own key a legitimate
later write.

Mutation-verified on this head: appending printf 'HOME=%s\n' ... to
integration_no_ak's 'Configure per-run npm cache (self-hosted)' step and
adding a step-level `env: HOME` each turn the suite red (103 pass -> 102
pass / 1 fail); reverting restores 103/103.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmw4ng9wvgi

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-06 16:41:48 +08:00
Shaojin Wen ef9c891b8f fix(ci): clear the pinned corepack cache before every install (#16351)
* fix(ci): clear the pinned corepack cache before every install

The Linux test shards have been dying seconds into Install dependencies
with MODULE_NOT_FOUND on the cached pnpm's bin/pnpm.mjs since #16328
pinned COREPACK_HOME to runner.temp (main runs 41876940141, 41889761157,
41927887452; six different self-hosted hosts). The pin assumed runner.temp
is per-run, but self-hosted runners keep _work/_temp across jobs — the
lint job's isolated-HOME step already documents this. corepack trusts an
existing cache entry without re-verifying its files, so a partial pnpm
tree left by an earlier job fails every later job on that host.

Clear the pinned cache at the start of the shared install body in all
three jobs that copy it, enforcing the per-run contract the pin
documents. The guard keeps the reset a no-op where COREPACK_HOME is
unset (test_build) or already run-unique (lint). The test step still
reuses the cache install warms in the same job, and poisoned hosts
self-heal on their next job without manual cleanup.

Pin the reset and the install/test COREPACK_HOME match in the workflow
meta-test, and bump the size baseline for the added lines.

* fix(ci): scope the corepack cache reset to the shard-owned path

Review found two over-deletion shapes in the nonempty guard: a runner
that exports COREPACK_HOME would lose its entire shared cache in the
build job, and hosted lint would lose the cache its pnpm-store step had
just warmed, re-exposing the install to a registry outage. Match only
the exact shard-owned path instead; the build and lint copies become
unconditional no-ops and the three install bodies stay byte-identical.

Add an execution test that runs the shipped guard fragment under a
scrubbed environment across five cache shapes: it kills the dead-guard
mutant the string pins survived, and a widened-guard mutant dies on the
shared-cache survival cell. State the incident as observations with the
corruption source undetermined, and correct the stale per-run rationale
beside the pin.
2026-11-05 09:27:53 +08:00
易良andQwen-Coder 2b9c4a98f4 fix(ci): pin COREPACK_HOME for Linux test shards on cold-HOME runs (#16328)
The Linux unit-test shards set HOME to a fresh per-run directory for
isolation. scripts/tests/corepack-warmup.js (vitest globalSetup) spawns
`corepack pnpm --version` before any worker forks, and on a cold HOME
corepack downloads the pinned pnpm from registry.npmjs.org. The ECS
egress intermittently times out on that fetch (ETIMEDOUT to 104.16.9.34),
which kills the shard after every test file has already passed — today
that produced 7 PR-facing failures across random shard numbers (2/8,
5/8, 3/8, etc.), all on the same self-hosted host.

Install dependencies runs before the test step and warms the corepack
cache under the runner's real HOME, but the test step's HOME override
makes that cache invisible. Pin COREPACK_HOME to a per-run path in both
steps so they share the same cache without touching the HOME isolation.

Per-run, not persistent: corepack does not re-verify extracted entries
on cache hit, so a shared cache would be a poisoning channel (the same
reason lint_and_static keeps its COREPACK_HOME per-run).

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-05 01:19:56 +08:00
易良andQwen-Coder b383efb854 fix(ci): widen the built-tree download window for eight shards (#16285)
* fix(ci): widen the built-tree download window for eight shards

Eight shards now pull the same unit-test-tree artifact at once, so the
download cap has to clear the blob's tail latency rather than the median.
On main the slow host class took 4m52s, and two consecutive runs expired
twice at the five-minute cap before the unpack ran, which failed the
required Linux check with no test involved.

Raise both attempts to fifteen minutes and pin the pair in the
build-to-shard handoff contract.

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): size the shard download cap from the measured tail

Review flagged that the outgoing comment claimed a pool host had stalled
"for over a quarter of an hour", which the new fifteen-minute cap then
contradicts. That claim came in with the sharding commit and was never
tied to a run; neither incident can prove how far past five minutes the
tail went, so the comment now states only the measured distribution.

Also correct the number: the slowest completed shard took just under five
minutes, and it was a different shard that expired, on two consecutive
runs. Add the missing shard-side ceiling contract, mirroring the build
job's: every blocking step cap has to fire before the job cap, because a
cancelled job skips the failure-gated disk-pressure collector.

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-11-04 17:41:55 +08:00
56fec7f3f5 ci: shard the Linux unit tests and web-shell smoke, and reuse cached builds (#16133)
* ci: shard the Linux unit-test lane eight ways behind one build

The Linux Test job ran 45-59 minutes: a ten-minute prepare build, then the CLI and core suites back to back. A new test_build job runs the prepare build once and uploads its outputs; eight shards install without the build, unpack them and run --shard=N/8 of every Vitest suite. Test (ubuntu-latest, Node 22.x) becomes an aggregate that passes only when the build and every shard pass.

* ci: record ci.yml's growth from the test_build prelude

test_build carries the same pre-checkout cleanup, checkout and install prelude as the test shards; ci-platform-lanes.test.js pins the two copies identical.

* ci: record the Linux lane's two new jobs in the PR-stage budget

test_build and the test_ubuntu aggregate add two jobs; the declared timeouts drop from 305 to 295 minutes.

* ci: shard the web-shell E2E smoke four ways

The smoke lane ran 44-46 minutes on every recent PR, about 33 of them in the browser tests at two workers, and is the longest lane once the Linux unit tests are sharded. Each shard runs --shard=N/4 of the smoke; the transcript gate runs on the first. web-shell E2E Smoke (ubuntu-latest, Node 22.x) becomes an aggregate over the shards.

* ci: bound the unit shards' tree download and retry it once

A pool host stalled 17 minutes in download-artifact while the other shards took under a minute.

* ci: rebuild only the workspaces a change reaches

scripts/ci-build-cache.mjs hashes each workspace's tracked build inputs (tests excluded) with its workspace dependencies' hashes and the shared root inputs. test_build and the web-shell smoke shards restore the newest cached build and rebuild only workspaces whose hash changed. A push to main always builds from scratch and saves, so a pull request starts from a clean build of main.

* docs(ci): describe the incremental build cache

* ci: keep the build cache path the same on every pool host

actions/cache versions an entry by its path; under runner.temp the path differs per host, so a run could never restore another host's save.

* ci: save the ARM64 build cache from the smoke lane, and run the aggregates on the shards' runners

The smoke shards run on the ARM64 pool, which test_build may not, so the first shard saves the clean build it made when its restore found nothing. The two aggregate checks queued 9 and 13 minutes for a hosted runner after their shards finished; they now take the shards' runners.

* ci: bound the sharded test step and skip shards when the build fails

Address the first automated review round on the Linux sharding lane:

- Gate every build-consuming shard step on `test_build` success. `!cancelled()`
  stays true when a needed job fails, so a failed build still started all eight
  shards, each paying a full install before dying at the missing artifact and
  reporting artifact plumbing as its failing step.
- Drop the test step cap to 45 minutes. The job cap is 60, so the old 110 could
  never fire; a step timeout keeps the diagnostic steps below it alive while a
  job timeout does not.
- Keep the built-tree artifact for a week so a late "re-run failed jobs" still
  finds it, and correct the trigger comment that still named three post-merge
  jobs when five now accept push.

Co-authored-by: Qwen-Coder <[email protected]>

* ci: keep disk telemetry for a failed build and correct the design doc

`test_build` installs with the disk sampler and then builds for up to its
whole timeout, but the sampler dies with the install step's trap and no
collector ran, so an ENOSPC build (or the OOM that took a shard host) kept
no evidence. Give it the same failure-gated dump and last-step upload as
`lint_and_static`, under its own artifact name.

The incremental-lint design still said the test job keeps normal prepare
behaviour; this lane now installs with `QWEN_SKIP_PREPARE=1` and unpacks
the tree built once by `test_build`. Correct both language versions.

Co-authored-by: Qwen-Coder <[email protected]>

* ci: share the built-tree definition and the build preflight, harden the guards

The archive step re-implemented `builtPaths()` from `scripts/ci-build-cache.mjs`,
so the tree the shards unpack had a second definition that could drift from the
one `pack` stores. A `paths` mode now prints that list for `tar -T -`, and a new
test pins the four artifact-name literals that tie the producer to the eight
consumers.

The cached branch of `build` also hand-reproduced a subset of `scripts/build.js`
and so skipped its ink-patch preflight: the same missing
`frame-controller.d.ts` self-healed on a cache miss and surfaced as ~50 tsc
errors inside ink's declarations on a hit. The preflight is now a function
reachable as `scripts/build.js --preflight` and runs before both branches.

The remaining review suggestions were all meta-test gaps: the standalone-runtime
expression and the `test_build`/`test` prelude were guarded by nothing that
compares them in order, `ci-build-cache.mjs`'s three CI modes were never
executed by a test, and the infra-classification filter's step name was not
joined to `ci.yml`. Each now has an assertion that goes red when the guard is
removed.

Co-authored-by: Qwen-Coder <[email protected]>

* ci: bound the build steps so the failure collectors can run

Round-2 review found four issues in the previous round's fixes and one gap
in the new build tests.

A job timeout cancels the job and skips the failure-gated collectors, so the
`test_build` steps that can block now carry caps below the job's 45 minutes:
the install, the build, the pack and the archive. The install caps are
identical across `test_build`, `test` and `lint_and_static` so the prelude
parity guard still compares the three copies field for field. A test pins
every blocking cap under the job ceiling.

The `--preflight` early exit was only asserted as a string, so it could be
renamed with nothing going red while the cache-hit path silently ran the
full build. A spawned case now copies `scripts/build.js` into the fixture
and asserts the flag exits without generate, a workspace build or the
schema step.

The CLI mode tests write extension-less `#!/bin/sh` stubs and join PATH with
':', which cmd.exe cannot resolve; the Windows lane collects that file and
downgrades its failure to a warning, so it would have carried a permanently
red block. The suite is excluded on win32 alongside the other bash-fixture
suites.

The `test_build`/`test` intersection grew to 16 with the disk collector, but
the floor stayed at 15, leaving exactly one shared step uncompared; the floor
now matches the membership. The cache-hit-with-changes branch — the code
that decides what is rebuilt and what is deleted — gains a case: a changed
workspace is purged and rebuilt by directory, and an untouched workspace
keeps what the cache restored.

ci.yml's recorded size baseline is updated to the current file so the next
PR gets the full growth allowance back.

Co-authored-by: Qwen-Coder <[email protected]>

* test(ci): keep cache fixture call logs outside the archived tree

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: 易良 <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
2026-11-04 14:52:12 +08:00
ca7d487ce9 ci: route Lint & Static to ECS for every pull request (#15974)
* ci: route Lint & Static to ECS for every pull request

* ci: isolate HOME and complete routing pins for lint_and_static

- lint_and_static admits untrusted forks to the shared pool, so pin a
  job-level HOME/USERPROFILE under runner.temp and create it before the
  first write (git safe.directory, npm config).
- Derive the classifier output from each job's own runs-on in the ceiling
  helper, so a timeout pointed at the wrong output throws instead of
  being substituted; parameterise runnerOn the same way.
- Pin test's own runs-on to ubuntu_runner and add the HOME-isolation
  guard for every lane routed past the association gate.
- Correct the qwen-autofix premise and the integration_no_ak comment,
  and record the routing decision in a bilingual design note.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvvwdojb45

* fix(ci): isolate lint lane HOME and restore routing guard strength

Resolve R1-1 (Critical): per-job HOME for lint_and_static so untrusted
fork code cannot plant git/npm config a later trusted job inherits;
shared warm caches re-pointed via getent + XDG_DATA_HOME/COREPACK_HOME.
Resolve R1-2: timeout ceilings derive the output name from each job's
own runs-on. Resolve R1-3: explicit ubuntu_runner pin for test.

* docs(ci): record lint ECS fork-routing design and correct stale premises

Resolve R1-5: bilingual design record (container tradeoff, acceptance,
residual risks), qwen-autofix.md section 38 premise, no_ak scoping comment.

* fix(ci): publish run-unique isolated HOME from step env, not job env

Resolve P1/R1-1: jobs.<job_id>.env cannot read the runner context, so the
workflow failed GitHub validation entirely; publish HOME/USERPROFILE via
GITHUB_ENV from the first step instead. Resolve R2-1: RUNNER_TEMP survives
across jobs on this pool, so the path is run-unique, cleared before create,
and reclaimed by an always() step before the failure collector. Resolve
R2-3: share content-verified linter/corepack caches via RUNNER_TOOL_CACHE.
Resolve R2-5: move the HOME guard into the PR-gated routing battery with a
single-lane set pin; resolve wenshao R2-4 with one fleet ladder for both
outputs. Witness: actionlint 1.7.12 exits clean on ci.yml; six mutations
each red the matching guard.

* docs(ci): correct lint routing record per round-2 review

Supersede link points at qwen-autofix.md section 38 (R2-4); read-only token
and HOME-relative write class stated precisely (R2-2); residual risks are
examples not a census, naming web_shell_e2e_smoke and other PAT pool jobs
(wenshao R2-3); association gate recorded as throughput policy, not an
enforceable boundary (R2-6); dispatch-over-fleet priority and per-lane
hosted ceilings (P3); cache cost list and PR-gated test placement updated.

* chore(ci): bump ci.yml size ratchet for merged tmpdir routing and HOME isolation

Growth is real: #15979's helper-test tmpdir routing arrived via the main
merge the freshness gate required, plus this PR's run-unique HOME
mechanism. 156377 -> 161281 bytes.

* fix(ci): keep COREPACK_HOME per-run; shared cache is a poisoning channel

Corepack does not re-verify extracted entries on cache hit (reproduced
locally on 0.36.0 with the repo's pnpm pin: tampered entry executes with
the recorded hash unchanged, network disabled). Move COREPACK_HOME inside
the run-unique HOME; XDG_CACHE_HOME stays shared because lint.js
SHA-256-verifies linter archives on every hit. Guard and bilingual record
updated; shared-corepack mutation reds the guard.

---------

Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: Shaojin Wen <[email protected]>
2026-10-31 16:35:58 +08:00
易良andQwen-Coder cd4c917fcf fix(ci): route helper-test tmpdir off the host /tmp tmpfs (#15979)
* fix(ci): route helper-test tmpdir off the host /tmp tmpfs

* test(ci): pin the helper-battery tmpdir routing against comments

Two gaps in the #15979 change:

- The pin used toContain/indexOf on the raw run block, so prefixing the
  export with `# ` disabled the routing while the guard stayed green.
  Filter comment lines before asserting, like the lint-history pin already
  does, and pin the private dir + EXIT trap the step now creates.
- The step routed TMPDIR straight into the shared RUNNER_TEMP root. Copy
  the unit lanes' second half too: mktemp -d under RUNNER_TEMP and reap it
  on EXIT, so a cancelled battery does not leave its fixture tree behind
  (its own after() hook does not run on a signal).

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvvyiud548

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-10-31 15:35:03 +08:00
易良 46d825d115 Revert "fix(ci): isolate web-shell smoke from shared runner networks (#15886)" (#15961)
This reverts commit cc6de38cb5.
2026-10-31 02:36:35 +00:00
易良andQwen-Coder cc6de38cb5 fix(ci): isolate web-shell smoke from shared runner networks (#15886)
* fix(ci): isolate web-shell smoke from shared runner networks

Co-authored-by: Qwen-Coder <[email protected]>

* docs(ci): clarify hosted smoke font guard behavior

Co-authored-by: Qwen-Coder <[email protected]>

* docs(ci): explain dormant smoke pool steps

Co-authored-by: Qwen-Coder <[email protected]>

* docs(ci): stop overstating which dormant smoke guards are pinned

The retained comment claimed every self-hosted step's guard is pinned by
checkout/preflight tests. Only two of six are: `Recreate runner Git
metadata` (scripts/tests/qwen-runner-checkout-workflow.test.js) and
`Use pre-installed Node.js (self-hosted)`
(scripts/tests/no-ak-integration-ci.test.js). `Check host font baseline
(self-hosted)` is named by no test file, and its
`runner.environment == 'self-hosted'` guard can never be true under
`runs-on: 'ubuntu-latest'` — so it is parked, not pinned.

Say which two are pinned and that the font baseline guard is parked, and
re-measure the ci.yml size ratchet entry (155625 -> 155708).

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvugxtx22a

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-10-30 19:43:04 +08:00
易良andQwen-Coder 84aee5b9b7 test(web-shell): include transcript coverage in recovery fixture (#15883)
* test(web-shell): include transcript coverage in recovery fixture

Co-authored-by: Qwen-Coder <[email protected]>

* test(web-shell): complete recovery event fixtures

Remove the unevaluated transcript type guard and supply required event terminal flags without changing assertions.

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): use runner temp for Web Shell browser smoke (#15884)

* fix(ci): use runner temp for Web Shell browser smoke

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): resolve runner temp in the browser step context

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): partition the complete macOS test suite into bounded jobs (#15888)

* fix(ci): use runner temp for Web Shell browser smoke

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): resolve runner temp in the browser step context

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): partition the complete macOS test suite into bounded jobs

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): account for the macOS aggregate job budget

Record the additional five-minute aggregate without changing any test execution timeout.

Co-authored-by: Qwen-Coder <[email protected]>

* test(install): isolate Windows fixtures from persistent PATH (#15889)

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): write the macOS shard matrix in block style and pin its gates

The seven-row matrix used YAML flow mappings, which `.yamllint.yml`
forbids (`braces: forbid: 'non-empty'`), so the `Run yamllint` step
cannot go green on this branch. Rewrite the rows in the block style
every other workflow matrix in this repo already uses.

Add the test pins the shard plumbing was missing:

- the `cli`/`core` dispatch argv, including the bare `--` that keeps
  `--shard` forwarded to vitest instead of consumed by npm, and the
  per-group `NODE_OPTIONS` choice
- `id: 'install'` on `test_macos_shards`, which the suite's
  `steps.install.outcome` gate silently reads as '' if it is renamed
- the aggregate `test_macos` gate against the shards' gate, so the
  preserved check name cannot report `skipped` while the legs fail

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvu6810z1y

* fix(ci): preserve shell fixture gates on macOS system Bash

Handle empty issue families under nounset without mapfile and explicitly reject either missing OSS credential. Keep failure assertions and include shell diagnostics.

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): keep macOS aggregate failures eligible for infra retry

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-10-30 18:24:19 +08:00
易良andQwen-Coder 9acb3a5a1d fix(ci): pin PR retries to the triggering head SHA (#15734)
* feat(managed-agent): materialize inert external workspaces

* feat(managed-agent): enable external files with verified close (#15685)

* feat(managed-agent): add the external-workspace provisioning prerequisite (#15512)

Step 3a of the external Hosted Workspace plan: a new `external-workspace`
Runtime provisioner kind that places a session-isolated worker on a registered
EXTERNAL storage tree and mints the boot document the shipped worker already
admits (`managed-context/1` plus the existing `storage` member).

The storage identity comes only from the durable registration: a read-only
primary-key lookup that admits `REGISTERED` rows, bounds the revision to
2^53-1, joins the owner row on its own primary key, and fails closed when a
persisted row disagrees with itself. A registration that cannot be read to
completion stays retryable (`runtime_external_unavailable`) so the Broker keeps
the placement adoptable instead of burying it. `external-workspace` accepts
only the closed evidence set {storageRef, mountRoot, continuity}; `mountNonce`
from the design is deferred and explicitly refused, because the registration
record cannot persist it yet and an unverifiable claim must not be ignored.

Hosted tools, EXECUTION admission, publication consumption, `stopDrained` and
close gating are not enabled: `supportsDrainedStop()` stays false, every
managed Workspace feature remains refused, and preflight now states the
prerequisite in an `external-prerequisite` warning. No Session can be placed
yet, and every placement fails closed.

`external-scratch` behavior and wording, the shipped worker
boot/storage/registration.json contract, the schema and the CLI are unchanged.

* test(managed-agent): pin external storage refusal in lock canary

* fix(managed-agent): require exact external workspace digest

* fix(managed-agent): reject storage evidence for non-running workers

* feat(managed-agent): add inert exact-worker external drain

* feat(managed-agent): fence external workspace ownership and drain

* feat(managed-agent): enable external files with verified close

* docs: record verified external files and remaining phase two work

* fix(web-shell): avoid recovery warning for closed sessions

* test(ci): pin HostedExternalPublicFilesIT in the hosted-process class list

#15685 adds HostedExternalPublicFilesIT, so hosted-process-ci.test.js's
expected class list must carry it; the meta-test pins which IT classes the
hosted-process lane runs and had 35 entries against 36 discovered.

* fix(managed-agent): restore admission precedence and honour a LOST drain claim

The external-files increment resolved the Workspace above the Agent pin, so a
request whose stored definition cannot take effect, or whose revision cannot be
selected, was refused on Workspace grounds first: it answered 404
workspace_required and a bare IllegalStateException instead of 409
agent_definition_unsupported / 400 unsupported_feature. Resolve the pin first
again and keep the external pin rewrite once the Workspace is known.

The close-claim validation admitted only DRAINING, but drainClaimedBinding
deliberately keeps a LOST binding in LOST until its stop proof commits, so the
arm refused its own claim with runtime_close_claim_pending instead of surfacing
the stop's runtime_broker_recovery_blocked. BindingRenewal now records that its
claim was taken on a LOST binding and admits that state.

The settlement probe reads the storage kind to refuse an EXTERNAL tree the
bridge holds no mount for, so its fixture needs the managed catalog rather than
an empty database. The lock-order canary gains the new ExternalWork.apply/3
ambiguity, whose callee locks the Session row first and never reads ma_turn.

* fix(ci): pin PR retries to the triggering head SHA

* docs(ci): correct two comments the head-SHA pin made stale

Pinning the four pull_request lanes to github.event.pull_request.head.sha
left two comments describing the old checkout: the one above test_macos
still cross-referenced the Ubuntu gate's superseded wording, and
check-lint-gate-freshness.mjs still said the lint lane checks out
refs/pull/N/head.

Comment-only. ci.yml shrinks by 5 bytes (156090 -> 156085), so neither the
.size-baseline ratchet nor the .ci-budget ledger moves.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvr2rokbyb

* test(ci): pin all four PR checkout lanes to their whole ref expression

The whole-expression `toBe` pin covered only `test` and `lint_and_static`.
`web_shell_e2e_smoke` and `integration_no_ak` were guarded by
`toContain(head.sha)` plus `not.toContain('refs/pull/')`, neither of which
can see arm order: moving `|| github.ref` ahead of the pull-request arm
keeps both green while the lane checks out the asynchronously rebuilt merge
ref this PR exists to get away from, and the ancestor guard still passes
because a newer merge tree contains the expected head.

Pin both lanes against their own full ref literal, so arm order is part of
the contract the same way it already is for the push-arm lanes. Verified one
mutant at a time on each of the two lines: the new assertion turns red
(1 failed | 79 passed) while the two substring assertions stay green, and
the tree is byte-identical after reverting.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvr7204nyh

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-10-28 10:22:22 +08:00
易良 e24f9c48fd fix(ci): deepen until the lint scope's own acceptance holds (#15644)
* fix(ci): deepen until the lint scope's own acceptance holds

The deepen loop stopped as soon as `git merge-base --all` exited 0, but
scripts/lint-scope.js also rejects an ambiguous merge base and a shallow
boundary above it. A branch that merged the base tip proves a merge base
immediately while its head-side boundary still sits above it, so the loop
broke with zero deepens and lint-scope fell back to a full scan with
three unused attempts. Mirror the consumer's acceptance instead:
ambiguity stops the loop (deepening cannot fix it), a shallow boundary
keeps it going.

The two give-up paths — cap reached and an aborted deepen fetch — end in
the same downstream warning whose remedy (a larger cap) is wrong for the
aborted one. Emit the deepen count and the base depth reached so they are
distinguishable in the log and the step summary.

Pins now assert the mechanism rather than a bare `--deepen=` substring:
the blobless deepen, the merge-base guard, the shared predicate call,
`|| break`, the 3-round cap, and the give-up line.

* fix(ci): report completed deepens and ambiguous history
2026-10-27 09:52:58 +08:00
易良 22c89204f0 fix(ci): deepen the incremental lint history until the merge base is provable (#15525)
A fixed 100-commit window expires in about a day at this repository's
merge rate, after which merge-base finds no common commit and every
longer-lived PR silently ran the full scan (measured when the finding
was filed: 68.7% of recently-updated branches were more than 100
commits from their base, median 196).

Keep the fetch bounded: after the depth-100 blobless fetch, deepen in
capped steps until Git can prove the merge base. Each deepen transfers
trees only, and the existing history-fallback warning still fires if the
cap is reached.

Verified with the literal step body under bash -e -o pipefail against a
local filter-capable server whose fork sits 300 commits behind the base
tip: the current single fetch leaves merge-base unprovable, the new loop
proves it after one deepen round and yields the expected changed-file
list.
2026-10-25 21:57:28 +08:00
易良 f885f39ee9 fix(ci): price ARM64 fleet lanes from the self-hosted label (#15529)
The two Linux lanes that raise their timeout on the shared self-hosted
pool -- the merge-gating lint lane and the no-AK integration gate --
decided "reserved pool" by looking for the x64 fleet's name inside the
classifier's runner label set. The ARM64 fleet renders
["self-hosted", "linux", "arm64"], which carries no fleet name, so the
same contended machines were priced as hosted: lint at 45 minutes and
no_ak at 30 instead of 90/60. An ARM run killed by the shorter ceiling
reads as an ARM compatibility failure, which is the signal the fleet
pilot exists to collect.

Scope on the routing property that matters -- self-hosted versus
GitHub-hosted -- and model the new key in the routing meta-test with an
explicit ARM64 case.
2026-10-25 21:41:55 +08:00
易良 cc49daa282 fix(ci): preserve independent validation for main push SHAs (#15508) 2026-10-25 16:51:04 +08:00
易良 10c036b655 fix(ci): keep the incremental lint history fetch blobless and visible (#15511)
Lint & Static can already hold a blobless merge base: Report test growth
fetches it with --filter=blob:none. A later plain depth-100 fetch then
fails resolving thin-pack deltas against blobs this checkout does not
have (fatal: missing blob object + remote did not send all necessary
objects), and continue-on-error turns that into a silent full scan:
4 of 8 sampled PR runs after #15381 merged, with 5 of 8 falling back to
full lint in total.

Fetch blobless too, and drop the recorded filter so later fetches in the
checkout stay ordinary. lint-scope reads only commits and trees.

Announce a history fallback as ::warning:: plus a step-summary line on
workflow runs, deduplicated across the seven lint.js invocations, so the
optimization can no longer stop working without a trace.
2026-10-25 16:31:34 +08:00
1a98854b76 test(ci): pin the disk-pressure collector revision and FULL_PAYLOAD run bodies (#15475)
* test(ci): pin the disk-pressure collector revision and FULL_PAYLOAD run bodies

Two deferred review findings from #10919 (issue #11032). Both are
assertion-only: no workflow file is touched.

The shared-prelude equality loop compares `test` and `lint_and_static` to
each other, so both jobs' disk-pressure collector moving to a different
`actions/upload-artifact` revision together leaves both serializations
equal and reports green. The SHA-format regex on `Upload Test Results
Artifact` is not a witness either: it accepts any 40-hex revision, on a
different step. Pin the literal revision instead, resolved per job
because the collector step carries the same name in both.

The FULL_PAYLOAD test pinned name, order and gate, but only the two
notices steps had a command body pinned: emptying `Run ESLint`'s `run:`
or repointing it at `true` left every suite green while a required check
stopped running the tool it is named for. Extend the existing loop with a
name -> load-bearing-substring map that is total over FULL_PAYLOAD, and
assert the substring rather than whitespace-exact text so reformatting
one of the multi-line blocks stays benign.

Verified by mutation in both directions. Against the pre-change suite,
moving the collector pin in both jobs, emptying `Run ESLint` and
repointing it at `true` all stayed green (44/44) -- that green was the
defect. With these assertions the same three mutations fail, and the
unmutated suite passes 45/45.

Refs #11032

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-issue-patrol/jmvmng32bjj

* test(ci): pin collector retention, payload gates and failure propagation

Addresses the four review findings on the pins this PR adds, all of them
the same class of gap: an assertion that proves a string is present but
not that the thing it guards still happens.

R1-1: `with.retention-days` was unpinned on both disk-pressure collectors,
so a coordinated edit reported green through the job-to-job equality loop
(the loop is blind to both sides moving together). Pinned per job as the
literal 14; the JUnit `Upload Test Results Artifact` step deliberately
retains 7, so no shared constant.

R1-2: map totality over FULL_PAYLOAD was only incidental — `toContain`
coerces a missing entry to the literal string "undefined", which a body
containing that word passes silently, and a stale key after a rename reads
as coverage that no longer exists. Asserted the key sets match instead.

R1-3: payload gates were filtered by a `ci_profile == 'full'` substring, so
appending any further `&&` clause dropped a step from the unattended
post-merge lane while every substring still matched. All 19 gates are
byte-identical, so pin the whole literal.

R1-4: containment proves the command is present, not that its failure
reaches the job status. Pinned `continue-on-error` absent and no
`|| true`/`|| :` in the body, plus `exit 1` in the settings-schema gate.
Both stay inside the loop: `Report test growth` and `Fetch incremental
lint history` set continue-on-error deliberately, and the helper-tests
retry legitimately uses `|| {`.

Each new assertion was shown to fail by mutating `.github/workflows/ci.yml`
one edit at a time: the six gap mutants pass 45/45 against the previous
head and each is killed by exactly the assertion added for it. The
one-sided retention control still trips the pre-existing equality loop, so
those greens were coordinated-edit blindness rather than a dead comparator.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvmwqg38tp

---------

Co-authored-by: yiliang114 <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
2026-10-25 12:50:50 +08:00
易良andQwen-Coder cdd5f1e027 ci: retain raw JUnit reports for same-repo runs (#15379)
* ci: retain raw JUnit reports for same-repo runs

* ci: keep the retained-report upload advisory and pin its guards

Review follow-up on e7f6f7ae45 (qwen-code-review-bot R1-1, R1-6, R1-4).

R1-1: widening this step from fork-only to every full-profile run also widened
its blast radius. A transient artifact-service failure now fails the step and
reddens Test (ubuntu-latest, Node 22.x) on a suite whose tests all passed, and
on a push to main that red also reaches main-ci-failure-issue.yml, which files
an issue about a failure that is not a test failure. The publisher two steps
above is already advisory for exactly this reason, so this matches the sibling
rather than inventing a policy. continue-on-error: true, asserted in the
existing advisory-vs-blocking case.

R1-6: the uses: guard accepted any ref, so relaxing the pin to a mutable tag
survived green. Tightened to a 40-hex SHA, the shape
main-ci-failure-issue-workflow.test.js already uses for actions/checkout. The
trailing "# v7.0.1" comment sits outside the parsed value, so anchoring the
regex to the value is sufficient.

R1-4: the new evaluate() injected hand-built stubs, so an unmodelled context
term read as undefined and compared silently -- github.event.pull_request.draft
!= true evaluates true and every row stays green while asserting nothing about
the new term. gateRunsOn and timeoutMinutesOn in this same file already throw
on residual context terms; evaluate now does too, reusing gateRunsOn's message
spelling rather than adding a third.

Each guard was mutation-checked separately on this branch, one mutant at a
time, with the files restored byte-identically afterwards:

  uses: -> actions/upload-artifact@v7   1 failed | 43 passed (44)
    AssertionError: expected 'actions/upload-artifact@v7' to match /^actions.../
  continue-on-error: true removed       1 failed | 43 passed (44)
    AssertionError: expected undefined to be true
  loud() neutered to identity           1 failed | 43 passed (44)
    AssertionError: expected [Function] to throw an error

Clean tree: Tests 44 passed (44), rc=0. check-ci-budget.mjs rc=0
("PR-stage CI budget holds") -- no timeout-minutes or workflow change here.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmvlhalgps3

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-10-24 14:44:31 +08:00
易良 412c7c307d ci: start the web-shell smoke lane beside Test instead of behind it (#15408)
The lane has depended on the Test job since it was created, for exactly one value: the normalized CI profile. classify_pr produces that value itself (its classifier forces an unknown one to full), so the edge buys ordering rather than trust: Test takes ~34 minutes and the smoke lane ~35, and every full-profile PR pays that queueing twice in series.

Gate on the classifier's output instead and both lanes start together. The '|| full' fallback keeps the old behaviour when the classifier emitted nothing, and the meta-test that evaluates this gate now models the term it reads.
2026-10-24 14:18:44 +08:00
易良 1f7ed6b50a perf(ci): scope PR lint and reuse static build outputs (#15381)
* perf(ci): scope PR lint and reuse static build outputs

* fix(lint): keep incremental selection a superset of the full lane

- Select .mts/.cts: flat-config --ext is additive to the config's own
  files scope, so lint:ci checks them; the selector dropped them and a PR
  touching only such a file reported green without opening it.
- Stop escalating the whole-repository ShellCheck for terminal-bench
  paths the lane itself greps out.
- Report every failing Prettier directory in one run, since Prettier runs
  per directory with basename targets and the first failure aborted the
  rest.
- Pin RUNNER_TEMP/GITHUB_* in the subprocess fixture so its setup cannot
  rmSync the ambient runner linter temp directory.

* fix(ci): fetch incremental lint history after the shallow refetches

Both 'Check the API contract version advances' and 'Report test growth'
refetch shallow history below the old position; a depth-1 fetch of the
base ref grafts the commit LINT_BASE_SHA names, so merge-base fails and
every contract-document PR silently ran the full scan. Pin the binding
and the step order, correct the freshness header that still claimed the
lanes run repo-wide, and record the per-file selection backstop.

* chore(ci): record the ci.yml size ratchet bump

Lint & Static failed on the workflow-size ratchet: ci.yml is 151690 bytes
against a recorded 147136 with a 4096-byte allowance. The overage is this
PR's opt-in step plus ci.yml growth that landed on main without a same-PR
bump, which is the drift the ratchet exists to surface. The comment above
the fetch step was cut to four lines with the mechanism moved into the
design docs, and the recorded size now matches the file.
2026-10-24 12:42:37 +08:00
易良 2b05c8bd99 fix(ci): route web shell smoke to ARM64 independently (#15364)
* fix(ci): route web shell smoke to ARM64 independently

* test(ci): pin the smoke lane's ARM64 routing in the meta-tests

The runs-on change left two scripts/tests assertions pinning the old
routing, so the Linux Test check and the nightly macOS/Windows lanes
would go red once dependency installation succeeded (AGENTS.md
guideline #3).

- ci-platform-lanes: evaluate the real runs-on expression and pin
  self-hosted -> ARM64 plus the hosted and missing-output fallbacks,
  instead of the old equality with the shared test lane.
- no-ak-integration-ci: replace the raw-YAML literal pin with a check
  that the lane still derives its runner from the classifier; the
  architecture override is pinned in ci-platform-lanes.
- ci.yml: restore the #11250 pool history the routing change deleted,
  marked as x64-specific rather than a verdict on ARM64.

* test: tolerate slow durable I/O in CI synchronization checks
2026-10-23 16:02:17 +00:00
Shaojin Wen b6195e1bba fix(ci): run the trusted web-shell browser smoke on the ECS pool (#14471)
* fix(ci): run the trusted web-shell browser smoke on the ECS pool

The web-shell E2E smoke is the second-largest user of the hosted ubuntu
pool: on 2026-10-17 it took about 27% of hosted minutes, two thirds of
that from same-repo PRs, while the hosted pool sat at its 20-job org cap
and the ECS pool idled.

#11250 moved the job hosted after four pool attempts failed in run
34081409590. Three of them ran the smoke with Playwright's unpinned
default of half the host's cores, 8 workers against hosted's 2, and timed
out broadly on visibility and clock waits across long-standing tests. The
fourth's transcript gate took 73 s against its 60 s budget while npm
install alone took 28 min on that host.

Route the job like test's again, so trusted runs use the pool and untrusted
fork PRs stay hosted, and pin the smoke at 2 workers, the hosted default,
so a pool job no longer scales its browser count to the host. Drop the
comments that described the pool steps as retained for a future reroute.

* fix(ci): recreate runner Git metadata before the smoke checkout

The smoke job now checks out on the reused ECS workspace, so it needs the
same pre-checkout .git removal as test, lint_and_static, integration_cli and
integration_no_ak. Otherwise actions/checkout reuses whatever the previous
job left, including a broken shallow boundary or a live post-checkout hook.
Hold the job to that rule in qwen-runner-checkout-workflow.test.js.

* fix(ci): install WebKit's system libraries on pool hosts that lack them

The first pool run of the web-shell smoke (run 39030597470, ecs-qwen-hk3-11)
passed 272 of 286 tests in 10.6 min at 2 workers, and the transcript gate
took 17.8 s against its 60 s budget. All 14 failures were mobile-webkit
browser launches: the host lacked GTK 4, GStreamer, libwoff2 and about 40
more WebKit libraries, which Playwright's install step had already
reported in its host validation.

When that validation reports missing libraries, run Playwright's
install-deps for Chromium and WebKit through sudo -n. An APT_CONFIG adds
a dpkg lock timeout and the hosted step's network timeouts. The packages
persist, so each host installs them once and later jobs skip apt. The
install step gets the hosted step's 30-minute bound.
2026-10-17 21:36:32 +08:00
Shaojin Wen 20de6bf12e fix(ci): give the hosted Test lane the same 120-minute ceiling as ECS (#14474)
* fix(ci): give the hosted Test lane the same 120-minute ceiling as ECS

The full unit suite no longer fits 60 minutes on GitHub-hosted runners.
Those runners take fork PRs from untrusted authors and everything when
the ECS kill-switch is on. On 2026-10-17, of the hosted full-profile Test
runs that ended on their own, 13 passed in 45-59 minutes and 17 were
cancelled at exactly 60 while still passing tests. Job 117375307261 is a
typical run: install 7.4 min, cli 32.5 min, core 10.2 min, then cancelled
six minutes into web-shell with the root scripts tests still to run. Each
cancellation burns 60 hosted minutes and usually a manual rerun.

Drop the per-pool split and use 120 on both pools. The test step's own
110-minute cap still bounds a genuine hang.

* ci: record the Test lane's fixed 120-minute ceiling in the CI budget

check-ci-budget sums only fixed timeout-minutes and counts an expression as
a job without a fixed timeout. Turning the test job's per-pool expression
into a fixed 120 therefore moves ci.yml from 180 minutes with five
expression timeouts to 300 with four. The effective ECS ceiling is
unchanged; the hosted one rises from 60 to 120 as intended.
2026-10-17 13:21:35 +00:00
易良 4d835faee5 fix(ci): document the shared-group supersede policy and keep main uncancellable (#14286)
* fix(ci): let main pushes supersede under the shared concurrency group

A concurrency group holds at most one running plus one pending run, and a
newer pending run replaces the older one even while cancel-in-progress
reads false. Back-to-back merges to main therefore already dropped each
other's run, only silently and one merge behind. Make the intent explicit:
only the newest tree matters and it covers every merged change — the same
stance e2e.yml already takes (#5859). Release branches stay uncancelled.

* fix(ci): keep main uncancellable and document the pending-replacement policy

Review on #14286 showed the flip cancels the in-flight main run: merges land
every ~18min against this ~30min lane, no merge queue is enabled, so this run
is the only fast witness of a merged tree, and its always()-gated coverage
upload would publish partial reports under the complete artifact name.
e2e.yml already measured that cancelling main starved the suite (67 of 100
push runs cancelled, 25 reported, #5859).

Restore the main exclusion and instead make the existing behavior explicit:
a newer pending run replaces the older one even with cancel-in-progress
false, so back-to-back merges already coalesce onto the newest tree, and the
in-flight run always finishes and reports — by design, not accident.
2026-10-16 22:53:31 +08:00
易良andQwen-Coder 70407feeb9 ci: run Java and Desktop checks daily or on demand (#14205)
* ci: run Java and Desktop checks weekly or on demand

Move Java compatibility and integration checks out of PR and push events. Group Desktop Shell checks with the existing packaging dry run, replace daily packaging with weekly validation, and retain manual dispatch.

Co-authored-by: Qwen-Coder <[email protected]>

* ci: report weekly Java and Desktop failures as issues

Include both weekly suites in the existing main failure analyzer and reuse its issue deduplication and autofix routing.

Co-authored-by: Qwen-Coder <[email protected]>

* ci: run Java and Desktop checks daily

Co-authored-by: Qwen-Coder <[email protected]>

* ci: format failure reporting workflow list

Co-authored-by: Qwen-Coder <[email protected]>

* ci: preserve lightweight guards after daily suite migration

Co-authored-by: Qwen-Coder <[email protected]>

* ci: align lint permission assertion and Java concurrency

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-10-14 16:15:03 +00:00
ytahdnand钉萁 df5470e430 feat(web-shell): Add workspace file browser and previews (#13878)
* feat(web-shell): Add workspace file browser and previews

* fix(web-shell): Restore source preview CI fixtures and notices

* fix(web-shell): Address file pane review blockers

* fix(web-shell): Resolve review regressions and strengthen file checks

---------

Co-authored-by: 钉萁 <[email protected]>
2026-10-13 09:38:47 +00:00
c5b0e1f37f feat(managed-agent): Add offline W1b recovery bundles (#13138)
* feat(managed-agent): Add hosted file history and undo

* fix(hosted): preserve file history fault gate contracts

* fix(cli): settle hosted file history refusals safely

* fix(hosted): recover file history baselines and settled turns

* feat(managed-agent): Add offline workspace recovery bundles

* fix(managed-agent): Preserve offline recovery source invariants

* docs(managed-agent): Record latest W1b dependency integration

* test(hosted): align reply-loss recovery assertions

* fix(managed-agent): Preserve invalidated W1b recovery cuts

* fix(cli): Fail startup on entry import errors

* fix(managed-agent): Improve offline recovery diagnostics and connections

* fix(ci): reserve hosted browser smoke time after installation

* fix(recovery): preserve resumable bundle and lease evidence

codex: address PR review feedback (#13138)

* fix(recovery): avoid migration collision with Hosted Hooks

codex: address PR integration feedback (#13138)

Preserve main V27 and move the unmerged W1b recovery migration to V28 without changing SQL bytes. Synchronize bilingual evidence and unsupported Hook recovery boundaries.

* fix(recovery): capture ordinary Hosted model resources

codex: address PR review feedback (#13138)

Include route and usage resources emitted by every Hosted model attempt, while preserving strict resource and settled-cut verification. Exercise the actual Session, Harness and activation producers in recovery regression tests and synchronize the bilingual recovery boundary and evidence notes.

* fix(ci): keep managed failover ports distinct

---------

Co-authored-by: Shaojin Wen <[email protected]>
Co-authored-by: yiliang114 <[email protected]>
Co-authored-by: qwen-code-dev-bot <[email protected]>
2026-10-02 10:06:28 +00:00
3f56f74a6a feat(managed-agent): implement durable Hosted Hooks (H2) (#13129)
* feat(managed-agent): implement durable Hosted Hooks (H2)

* style(runtime-broker): remove blank line rejected by Checkstyle

* test(managed-hooks): align CI guards with H2 enablement

* fix(managed-agent): select current Turn by admission sequence

* fix(hooks): keep managed command environment out of argv

* fix(serve): close Hosted Hook recovery and capacity gaps

* fix(serve): settle cancelled startup hooks and identify deletions

* fix(hooks): settle cancelled callbacks and retry journal commits

* test(hosted): exhaust identical Store commit retries in fault gates

* fix(hooks): retain HTTP completion evidence after managed cancellation

* fix(hooks): settle cancellation before occurrence planning

* fix(hooks): settle proven-unstarted pre-tool cancellations

* fix(hooks): recover initialization and bounded cancellation results

* fix(hooks): settle cancelled initialization and bounded tool results

* fix(ci): bound hosted browser dependency installation

* fix(ci): record the ci.yml growth in the workflow size baseline

The hosted-browser install step adds two lines to ci.yml, taking it to
147136 bytes: 4110 over the recorded 143026 and 14 past the 4096-byte
allowance, which is why "Check workflow file size" failed. Most of that
growth is main-side drift the allowance had been absorbing, but this PR
touches the file, so the ratchet's stale-baseline leniency does not apply
and the number has to move in the same PR. The growth is real — a bounded
apt/Playwright install — so bump the entry instead of shrinking the
workflow.

Verified locally: .github/scripts/check-workflow-size.sh exits 0 against
base a7deb01bcb, and scripts/tests/workflow-size.test.js passes 220/220.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-conflict/jmupvw3d00b

* fix(hosted): refuse cancellation with an unknown execution outcome

* fix(ci): reserve browser smoke time after slow dependency downloads

* fix(ci): keep time for smoke after slow hosted installs

* test(web-shell): wait for step expansion before selecting next group

* test(ci): align hosted browser timeout contract

* fix(hooks): preserve recovery owners and diagnostics

---------

Co-authored-by: yiliang114 <[email protected]>
Co-authored-by: jinjing.zzj <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
2026-10-02 00:43:57 +00:00
qwen-code-dev-botandqwen-code-bot ff5148e69e fix(ci): retry the helper-tests step once against pool contention (#12772) (#12780)
* fix(ci): retry the helper-tests step once against pool contention (#12772)

The Lint & Static lane failed on main at 939b4db6bc — a commit whose only
change is a web-shell CSS rule — in 'Run .github/scripts helper tests' on
ecs-qwen-hk3-6. The same step passed on the same fleet 32 minutes earlier
and again on the next main run, the failing run's step duration matches the
green run's exactly (55s), and the full 588-test battery passes repeatedly
on a quiet host. This is the fleet's documented contention signature: the
same commit run on different hosts fails different steps (#10490), and a
host can run the identical suite 5x slower (#10868).

The unit lane's answer to that condition was VITEST_RETRY (#10868), but the
helper battery cannot take that shape — node --test has no retry flag — and
main pushes have no flaky-rerun patrol (that patrol only re-runs PR lanes).
So give the step one inline retry: a transient host hiccup now costs one
55-second re-run instead of a red main and an autofix issue, while a real
break still fails both attempts. The github_ci_only fast lane stays
single-attempt; it only runs on PRs, which the patrol covers.

Pin the retry in ci-platform-lanes.test.js; the pin fails when the retry is
removed (mutation-probed).

* fix(ci): scope the helper-retry warning to the absorbed flake (#12772)

Review round 1 on #12780 (three Suggestion findings):

- The ::warning:: fired before the retry's outcome was known, asserted a
  contention cause the incident log does not support, and named no suite,
  so a deterministic break shipped an annotation blaming pool noise while
  an absorbed flake left nothing countable. Attempt 1 now tees to
  $RUNNER_TEMP/helper-attempt1.log and the warning moves after the retry,
  gated on its success, naming the attempt-1 `not ok` lines — the datum
  #12772 asked for — mirroring e2e.yml's #11364 doctrine. pipefail from
  defaults.run.shell keeps node's status through the pipe; a double
  failure still reddens the lane with no annotation.

- The pin asserted the retry's shape, not its contract. It now also pins:
  the same battery on retry (no HELPER_TESTS_DEP_FREE), an unswallowed
  exit status (no `|| true`, no continue-on-error, the block still ends
  with `}`), and the warning gated behind the retry with the captured
  failures interpolated. `runs every invocation serially` now splits per
  `node --test` occurrence instead of one substring per step, so a second
  invocation missing --test-concurrency=1 — even on the same line — fails
  it. 9 mutations probed, all caught; the updated pin is red against the
  pre-round workflow.

- The fast-lane comment claimed the flaky-rerun patrol re-runs PR lanes;
  the patrol is best-effort and currently no-ops because
  ci-flaky-rerun.mjs fetches job logs without --allow-escape-sequences
  (the sibling fetch in main-ci-failure-issue.yml passes it). That bug
  and the heartbeat suite's escaped-subprocess teardown race behind the
  original flake are follow-ups outside this PR's footprint, recorded on
  the deferred-findings list.

---------

Co-authored-by: qwen-code-bot <[email protected]>
2026-09-27 03:18:36 +00:00
易良andyiliang114 a5a5eb556b ci(web-shell): give E2E Smoke 30 minutes (#12593)
* ci(web-shell): give E2E Smoke 30 minutes

The job now runs 184 smoke tests after about 7.5 minutes of setup and
ends right at its 20-minute limit: on #11206 three runs passed every
test and were then cancelled during cleanup or the last test.
Raising the limit stops passing runs from reporting red; sharding the
suite is the fix at the source.

* test(ci): pin the E2E Smoke limit at 30 minutes

The lanes test records every job's limit with its reason; update the
web-shell E2E Smoke entry to the new value and the runs behind it.

---------

Co-authored-by: yiliang114 <[email protected]>
2026-09-24 04:23:30 +00:00
易良 ab526a9e87 test(ci): cover the review-scratch suite in the yaml import ratchet (#12259)
`review-scratch-cleanup.test.mjs` imports `yaml`, but it was missing from the
`yamlSuites` list that pins the dependency-free fast lane off npm-package
suites. The ratchet therefore could not catch that suite being moved into the
lane that runs before any dependency install, which is the one failure mode it
exists for. Add it, and correct the stale count in the comment above.
2026-09-19 15:30:58 +00:00
7556821e65 fix(ci): retry transient E2E build artifact upload failures once (#11364) (#11375)
* fix(ci): retry transient E2E build artifact upload failures once (#11364)

* test(ci): harden the e2e-build upload retry contract pins (#11364)

Address review findings on the retry helper suite: scope the two-attempt
assertions to the archive instead of every upload-artifact step and bind
the attempts by step name, pin the pack-to-upload handoff (archive name
and step order), pin the build job's absent job-level continue-on-error,
pin the retry gate by whole expression so a failure() conjunct cannot
silently disable it, compare the two with: blocks wholesale (minus the
intentional overwrite asymmetry) plus the action pin, and compare
downloaded names against every workflow upload instead of restating the
four known legs. Also add the suite to the yamlSuites roster in
ci-platform-lanes.test.js so it can never join the dependency-free fast
lane undetected.

* fix(ci): correct the upload-retry rationale and name-key its contract (#11364)

Real-runner probes on the PR showed the comment's two reasons for the
retry's overwrite do not reproduce: a stalled upload aborts before
finalize and reserves nothing, and name uniqueness is scoped per run
attempt so a manual re-run does not 409 either. Reword both comments to
the window overwrite actually covers — a first attempt that finalized
e2e-build and only then reported failure.

The contract test picked "the archive" by position (uploads[0]),
contradicting its own comment: an unrelated upload step placed before
the archive upload reddened the suite with misleading messages. Key on
the literal e2e-build name instead — already the pinned contract in
scripts/tests/e2e-workflow.test.js — in both the filter and the
download-leg assertion. Mutation probes: a decoy upload step before the
archive now stays green, while decoy+retry-deleted, retry-deleted,
retry-renamed, and overwrite-dropped all still fail.

* test(ci): pin the pack-to-upload directory in the e2e retry contract (#11364)

Co-authored-by: Qwen-Coder <[email protected]>

* test(ci): pin the exact upload path in the e2e retry contract (#11364)

Round 3's handoff pin closed the pack side but left the upload side
pinned to a directory prefix, so moving only the two upload with.path
values into a subdirectory of runner.temp kept the suite green while
the runtime upload found no file and redded the build job. Compare
first.with.path to the exact '${{ runner.temp }}/' + archive literal,
reusing the derived archive binding rather than restating the name.
Mutation probes: the upload-side drift now fails on the new assertion
(16/1, expected-vs-actual), the workspace-escape and pack-side drift
mutations stay red on their existing assertions, and the same
upload-side mutation under the old prefix assertion stays green —
the exact pin is the load-bearing one.

Also reword the overwrite rationale in both the workflow comment and
the test comment to the form the shipped action source supports: the
record is created before any bytes move, so a failed first attempt can
leave the e2e-build name taken, and overwrite is delete-then-upload
and a no-op when nothing exists. The previous wording asserted a
stall reserves nothing, which the create-then-upload lifecycle
contradicts. And name the retry contract's actual ungated siblings
(run-e2e-tests.sh's sandbox-image build retry,
build-and-publish-image.yml's image build retry) instead of the
budget-gated sandbox:none shard retry the comment over-claimed parity
with.

* fix(ci): pin the unpack archive name and restore the overwrite rationale (#11364)

Round 4's handoff pin derives the archive name from the upload
with.path, so renaming the tarball at the pack step and both upload
paths while the four legs' unpack arguments stay put kept the suite
green while every leg would fail closed on main (review R1-3). Pin
each 'Unpack build artifact' run's trailing argument against the
derived name; the legs download into runner.temp/e2e-build/, so the
comparison is a suffix match, not the upload's full path. Mutation
probe: a producer-only rename now fails the contract with 'a leg
unpacks a different archive than the build job uploads' (16 pass / 1
fail), the unmutated workflow stays green (17/17), and the same
mutation was green before this pin (17/17).

Also restore the overwrite rationale that 03b921ba landed and 09deb2b
rewrote: the real-runner harness on this PR showed a stalled attempt
aborts before finalize and reserves nothing, and overwrite
(ListArtifacts -> DeleteArtifact) cannot rescue a name an un-finalized
attempt reserved, so the comments once again name the window overwrite
actually covers — an attempt that finalized e2e-build and only then
reported failure.

* fix(ci): announce absorbed e2e-build upload stalls so recurrence stays countable (#11364)

The upload retry absorbs a stalled first attempt behind
continue-on-error, so the build job concludes success and the main-CI
failure tracker — gated on workflow_run.conclusion == "failure" and
selecting jobs with conclusion == "failure" — never records the stall
the retry recovered from, freezing the recurrence count the #11364
ruling conditions on at one. Add a plain run step after the retry,
gated on steps.upload-build.outcome == 'failure', that emits a
::warning:: annotation: the same check-run surface that named run
34208365262's "Upload progress stalled.", so the absorbed stall stays
queryable without reddening the job or the four legs behind
needs: ['build']. The implicit success() gate from its placement after
the retry scopes it to the absorbed case; a double failure reddens the
job directly and the tracker already sees that.

Pin the step in the retry-contract test per the round-7 review's
acceptance criterion (R4-1): exact gate expression, ::warning::
emission, no action and no failing directive, placed after the retry.
Mutation probes: deleting the step, stripping ::warning::, negating
the gate, adding exit 1, moving the step before the retry, and giving
it a uses: action each turn the suite red (16 pass / 1 fail); the
unmutated workflow is green (17/17).

---------

Co-authored-by: Shaojin Wen <[email protected]>
Co-authored-by: qwen-code-dev-bot <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
2026-09-11 02:05:02 +00:00
Shaojin Wenand易良 70cf363395 feat(web-shell): Improve split-view session navigation (#11250)
* feat(web-shell): Improve split-view session navigation

* fix(ci): Run Web Shell browser checks on hosted runners

* fix(web-shell): Keep split navigation outside approval shortcuts

* fix(web-shell): Address split-view review regressions

* fix(web-shell): Stabilize split approval reports

* fix(web-shell): Preserve history anchors during slow rendering

---------

Co-authored-by: 易良 <[email protected]>
2026-09-08 17:33:19 +00:00
jinye f5ce3ea8fd fix(ci): give web-shell E2E Smoke the ECS timeout allowance its siblings have (#11212)
Four jobs route to the self-hosted pool through the same `ubuntu_runner`
expression. Three of them scale `timeout-minutes` for `ecs-qwen` — test 120/60,
lint_and_static 90/45, integration_no_ak 60/30 — and `lint_and_static` carries
a comment explaining why: the pool's contention, with hosted runners kept on the
tighter ceiling so a genuine hang there does not burn the ECS allowance.

`web_shell_e2e_smoke` was the fourth, and the only one still priced flat at 20.
It lands on the same hosts under the same contention and got none of that
allowance, so it was beheaded twice on a single commit that changes nothing it
tests: on hk3-9 `npm ci` alone ran 19m46s of the 20 and never finished, and on
hk4-23 install took 10m33s against 4m53s on hk5-2, leaving the browser smoke
8m15s before the job died against 2m29s warm.

Both phases running 2-3x is the pool rather than the dependencies. A cold npm
cache would explain a slow install but cannot explain the smoke phase, which is
a vite dev server plus a headless browser and touches no cache at all. The
healthy path is 8-10 minutes, so a flat 20 only ever tolerated a 2x slowdown —
exactly what the contended hosts deliver, and the per-host medians already
measured in #10879 (hk3 34.6 min, hk4 27.5, hk5 12.3) say which attempts were
going to die before they started.

40 gives the ECS path the same 2x its siblings use and covers the measured
worst case with room for a 3x; hosted keeps 20 for the reason lint's copy gives.

The lane was not covered by the guard that exists for exactly this regression.
`ci-platform-lanes.test.js` already evaluates the real timeout expression for
both routings so that, in its own words, "a regression to an unconditional
ceiling fails here instead of reading as a passing constant" — but it pinned
only the three siblings. Add this one on both routings.

Three mutants applied, all killed by exactly the intended test: reverting to the
flat 20, reducing the ECS allowance to 30 (under the measured worst case), and
raising it to a flat 120, which the hosted-ceiling test also catches.
2026-09-06 23:25:41 +00:00
703678136a perf(cli): import core modules directly instead of the package root (#10957)
* perf(cli): let tests resolve core modules individually, and stop two files importing the whole package

Importing from the core package root pulls in its entire export graph — a bit
over six hundred modules — however little of it a file actually uses. In a
release run the cli workspace spent 2223s collecting modules against 1372s
running tests, and a file that imports the package root costs about 11.5s
before its first assertion where one importing a single module costs about 2s.

cli's tsconfig already maps a wildcard subpath onto core's sources, so esbuild
resolves per-module imports when it bundles. Vitest does not read tsconfig
paths, and the alias list that stands in for them named only four subpaths, so
those imports did not resolve under test at all. This adds the wildcard there.

Expressing the alias list as an ordered array is what allows a pattern entry.
The package root has to become an exact match in the process: as a string it
would also match everything beneath it and rewrite each subpath into a path
under index.ts.

Two files move to per-module imports as a first check that the mapping holds
end to end. Both were picked because nothing that depends on them replaces the
core package with a mock factory — where a test does that, the mock stops
intercepting once the code under test imports the module directly, so those
call sites need their mocks moved in the same change and are left alone here.

* fix(cli): restore the named core subpaths the previous commit dropped

The previous commit was assembled from a working copy that predated main by
several weeks, so it silently reverted this file to that older state. Four
named core subpaths added since — envVarResolver, noFollowOpen,
subSessionConstants and toolWriteOrigin — disappeared with it, and the new
wildcard then claimed those specifiers and pointed them at files that do not
exist. 257 test files failed to load as a result.

All eight named subpaths are restored and kept ahead of the wildcard, with a
comment saying why that order matters and what a contributor adding a ninth
has to do. None of the eight can be derived from its specifier, so none of
them can be folded into the pattern.

The two migrated source files are rebuilt on their current contents for the
same reason; one of them had also been reverted by a line.

* perf(cli): import core modules directly where no test mocks the package

Importing from the core package root evaluates its whole export graph — a bit
over six hundred modules — however little of it a file uses. On the release
lane the cli workspace spends more time collecting modules than running tests,
and on the main lane it now takes 84 minutes on its own, most of it collection.

These 130 files ask for named modules instead. They were chosen by checking,
for every test whose module graph reaches them, whether that test replaces the
core package with a mock: a test that swaps the package wholesale stops
intercepting once the code under test imports a module directly, and a test
that spreads the real package and overrides a few names only matters if one of
those names is what the file imports. Files with either kind of coupling are
left for a later change that moves the mocks at the same time.

Only import statements move; every other line is byte-identical.

* fix(cli): point ProviderModelConfig at the module that declares it, and wrap long imports

Two problems with the previous commit, both found by CI.

The symbol map resolved a re-exported name to the module that re-exports it
rather than the one that declares it, so `ProviderModelConfig` was asked of
`models/types` when it is declared in `providers/types`, and the build failed
to typecheck. A checker now confirms, for every generated specifier, that the
named module really does export that symbol — following its own re-exports —
and it reports one bad pair out of 492.

The formatting pass that was supposed to run over these files had silently
done nothing: invoked from the repository root against paths outside it,
Prettier skips the files and still reports success, so long import statements
went out unwrapped. Rerunning it properly reflows 47 files.

Two files are left with an over-long line Prettier would wrap, because that
line is over-long on the base commit too and the lint gate does not flag it;
reformatting it here would be unrelated noise. Every other line outside an
import statement stays byte-identical.

* perf(cli): import core modules directly in another 114 files

The same mechanical change as the previous commit, over the files a corrected
reading of the test suite showed were always safe to move.

The earlier pass classified a test as replacing the core package if the text
of such a call appeared anywhere in it, including inside a comment. One file
only mentions the pattern in a doc comment explaining why it deliberately
avoids it, and being counted as a blocker there ruled out 217 modules that
nothing actually blocks. Ignoring comments when detecting the call raises the
number of files movable without touching a single test from 141 to 260.

Every generated specifier is checked against the exports its named module
really has, following that module's own re-exports — 865 pairs here, none
wrong. Outside import statements every line is byte-identical.

* test(cli): move three barrel mocks onto the modules they actually stub

Where a test replaces the whole core package with a factory, the code under
test cannot move to per-module imports on its own: the mock would stop
intercepting and the real implementation would load instead, quietly changing
what the test exercises while leaving it green. The mock has to move in the
same commit.

These three are the cases where that is unambiguous — every name the factory
stubs is declared in one module, and the code under test imports exactly those
names. Each pair moves together onto that module.

The pattern generalises: about sixty tests each hold back one or two modules
this way, and roughly ninety more modules are held by several tests at once
and need them changed together. Establishing the shape on the clean cases
first keeps the ambiguous ones honest.

* Revert the migrated modules that any failing suite depends on

Retargeting this PR at main got the unit suite to run for the first time, and
it failed: 16 files, 127 tests, none of them failing on the jsdom PR that
shares the same base. So they are this stack's doing.

Every one is the same shape — a stub the suite installs no longer intercepts
once the code under test names a module instead of the package root. The
static analysis that picked these files models three ways of installing such a
stub and misses at least two more: a spy planted on a namespace import of the
package, and whatever six of the sixteen suites do, which it cannot parse at
all. Sharpening the heuristic further is not the answer; it was already wrong
in a way no amount of local reading would have caught.

So this restores every migrated module that any failing suite reaches, 146 of
them, and keeps the 110 that nothing failing depends on. That is blunt — some
of the 146 are certainly fine — but it is the version that can be shown to
pass, and picking the survivors apart is work for a run that is green to begin
with.

* fix(core): let consumers outside the cli package resolve a core module by path

The integration gate failed to compile against the migrated files:

  error TS2307: Cannot find module '@qwen-code/qwen-code-core/utils/debugLogger.js'
  error TS2307: Cannot find module '@qwen-code/qwen-code-core/utils/editor.js'

Only packages/cli maps these specifiers, through a wildcard in its own
tsconfig. The integration suite lists the eight named subpaths and no
wildcard, and the package's exports map has entries for those same eight plus
the dist and src trees — so anything resolving the normal way, this suite and
any consumer of the published package alike, cannot name a core module.

Both gaps close here: the wildcard is added to the integration suite's path
mappings, and a catch-all maps a bare module path onto the build output. The
catch-all exposes nothing new; `./dist/*` already reaches the same files.

This is the part of the change with consequences beyond the test run. The
shipped CLI is a single bundle and never resolves these specifiers at runtime,
but the package is published, and until now a migrated import was only
resolvable from inside the one workspace that happens to map it.

* fix(cli): repair the mock pairs the revert split, and keep dev on source

Three findings from review, all of them consequences of earlier steps here.

Two mock pairs were left half-migrated. In one, the mock and the code under
test both moved to the module, but the suite's own import of the same two
functions still read the package root — so the suite held the real functions
while the code held the stubs. In the other, the revert restored the code to
the package root and left the mock pointing at a module nothing imports any
more, which stubs nothing at all. The first is completed, the second put back.

The exports catch-all also changed what `npm run dev` runs. Its loader
intercepts the exact package root and nothing else, so subpath imports fall
through — and where they used to fail to resolve, they now quietly reach
compiled output from whenever the tree was last built. The loader now redirects
them to the source tree when the source file is there, leaving the named
subpath exports, whose file names do not mirror their specifiers, to resolve
as before.

* Cover the exports catch-all, and stop it hiding from the architecture rule

Three review suggestions, all about the `./*` entry this branch added to core's
exports and the things that quietly depend on it.

Nothing exercised that entry. In the repo, cli's subpath imports resolve
through tsconfig paths or vitest aliases; in the published package neither
exists and Node resolves them against `exports`. So removing the entry, or
renaming the dist root, would leave every suite green and break `qwen` on its
first core subpath import. A new check runs Node's own resolver against the
built package — deleting the entry turns it red, which is the property that
makes it worth having.

The entry also widened what the utils-layer rule has to police. It resolved
self-references by exact key only, so a deep specifier like
`.../config/storage.js` matched nothing, returned early, and reported nothing —
while resolving perfectly at runtime. It now follows Node's own order: exact
keys first, then the longest literal prefix among patterns. Fixtures cover the
wildcard idiom, exact-key precedence, and a sibling utils import that must
still be allowed.

Finally the integration suite's path mappings. The guidance comment above them
said a bare wildcard falls through to dist, which stopped being true when the
wildcard was added — nodenext substitutes `.js` for `.ts`, and the program
resolves core subpaths through it to source today. Left alone, a maintainer
following that comment would delete the wildcard as a violation and silently
re-route live imports to dist declarations. The comment now describes what the
block does, and says why the six alias-style entries cannot be folded into the
pattern. Two entries that had drifted out of sync with core's exports map,
toolWriteOrigin and envVarResolver, are restored.

* fix(cli): point the session-picker branch mock at the module it imports

Review finding R1-4b. StandaloneSessionPicker now takes getGitBranch from
@qwen-code/qwen-code-core/utils/gitUtils.js, but the colocated suite kept
mocking the package root, so the override no longer intercepted anything the
picker resolves and the factory's importActual still pulled core's whole
index into the suite's module graph. The mock now names the same specifier
the component imports, and a small pin test goes red if the pair splits
again.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmtme4yqxhl

* fix(dev): preserve source-backed core entry paths

Co-authored-by: Qwen-Coder <[email protected]>

* fix(scripts): close core subpath verification gaps

Co-authored-by: Qwen-Coder <[email protected]>

* fix(scripts): gate dep-only core subpath exports in the check

The exports check collected specifiers only from packages/cli/src, but
acp-bridge and sdk-typescript are runtime dependencies of the cli whose
compiled dist keeps core subpath specifiers verbatim. Entries named only
by those packages (./goalWire, ./transcriptRecords, ./subSessionConstants)
were therefore ungated: deleting one kept every gate green while
`node packages/cli` died with ERR_MODULE_NOT_FOUND on the serve/replay
path. Extend the source scan to packages/acp-bridge/src and
packages/sdk-typescript/src so all 95 collected specifiers resolve
through the exports map. Verified: removing ./goalWire or
./transcriptRecords from packages/core/package.json now makes the check
exit 1 (entries restored after the mutation probe).

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmtmxfessij

* docs(harness): note terminal-capture loader is manually exercised

No CI test executes the terminal-capture harness loader's subpath
redirect; it runs manually only via `npm run test:terminal-bench`.
Record that at the redirect site and point at
scripts/check-core-subpath-exports.mjs as the CI gate for the real
resolution path, so the `named` map stays consciously in sync with the
named entries in packages/core/package.json.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmtmxfessij

* fix(scripts): reject core subpath targets outside the published dist

The exports check validated resolved targets against the repo tree, but
core's exports map also carries "./src/*": "./src/*" while package.json
publishes only dist, vendor and scripts/postinstall.js. A specifier
routed through that entry resolved to a real in-repo packages/core/src
file, passed the bare existsSync and exited 0 even though the published
artifact ships nothing — the installed CLI would die at startup with
ERR_MODULE_NOT_FOUND while the gate stayed green. Require the resolved
target to lie inside packages/core/dist/, where every legitimate
runtime specifier lands.

Add scripts/tests/check-core-subpath-exports.test.js: a fixture-tree
suite (per the scripts/tests convention) that runs a copy of the real
script in a temp workspace. It pins the dep-only scan from a18ffebb8
(goalWire named by no cli source resolves; removing its exports entry
exits 1 — the mutation witness requested in review, made hermetic) and
pins this guard (a ./src/*-routed specifier is reported as not
published; removing the guard turns that test red).

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmtn1pqbuir

* fix(scripts): allow published core package.json in subpath gate, cover probe stages

The round-4 dist-containment guard rejected
@qwen-code/qwen-code-core/package.json, which core's exports map
deliberately publishes ("./package.json": "./package.json") and npm ships
regardless of "files". Allow that one target; ./src/* targets stay
rejected (existing test remains red without the guard).

Also extend the fixture suite to the two failure stages it did not
witness: the named-export probe stage (target exists but lacks the probed
export) and the resolve-failure branch (exports map without the ./*
catch-all). Mutant-verified: removing the probe import/export-name check
or the resolve-catch failed++ turns the matching new test red and nothing
else.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmtngpuscjl

* fix(terminal-capture): add conversationsRuntimeMarker to harness loader map

The loader's named map listed 8 of the 9 named entries in core's exports
map: conversationsRuntimeMarker (source at utils/conversations-runtime-marker.ts,
a path the specifier does not mirror) was missing. The specifier is
reachable in the harness graph today via shared-env-keys.ts, so resolution
fell through to the exports map — ERR_MODULE_NOT_FOUND mid-capture on a
fresh clone, or a stale dist silently mixed into a source-backed capture
otherwise. Add the entry, matching packages/core/package.json and the cli
vitest alias.

Also add a sync guard (scripts/tests) asserting every named key of core's
exports map has a loader entry pointing at an existing core source file,
so the next omitted entry goes red in CI.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmtngpuscjl

* fix(integration-tests): map conversationsRuntimeMarker to core source in tsconfig paths

The ninth named exports entry was missing from the paths block, so
`@qwen-code/qwen-code-core/conversationsRuntimeMarker` (imported by
packages/cli/src/config/shared-env-keys.ts) fell through the wildcard to
a nonexistent substitution and resolved against packages/core/dist — or
failed with TS2307 on an unbuilt tree — the exact stale-dist mode this
block's comment warns about. Add a sync gate asserting every named
exports key whose target stem differs from the key has a paths entry
pointing at the matching source file.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmtntktgsk8

* test(scripts): assert loader map values and reverse key-set against core exports

The harness loader sync gate only checked key coverage (exports key ->
loader entry) and file existence, so two drifts passed it green:
a retargeted exports entry left the loader serving the old module (the
named map short-circuits ahead of the stem probe), and a stale
loader-only entry short-circuits captures on a module graph the shipped
package can no longer resolve. Assert each loader entry equals the
exports import target mapped into source space, and every loader entry
has a matching exports key. Mutating either direction now fails the
suite.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmtntktgsk8

* test(scripts): make the exact-key-precedence fixture verdict-sensitive

The rule verdicts by directory layer and never checks file existence, so
the goalWire fixture reported one violation whether the exact exports
key or the ./* wildcard resolved it — removing the exact-key branch of
resolveExportTarget left the test green. Switch the fixture to
transcriptRecords, whose exact resolution lands inside utils/ (allowed)
while the wildcard resolution lands outside (violation), so dropping the
exact-key branch now flips the assertion from 0 to 1.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmtntktgsk8

* docs(cli): correct the subpath-alias and mock-wiring comments

packages/cli/vitest.config.ts named the cli tsconfig `paths` block as the
trigger for adding a named core subpath alias, but only three of the nine
entries (noFollowOpen, subSessionConstants, transcriptRecords) appear there;
the source of truth is the named keys of the `exports` map in
packages/core/package.json. The comment now says so, and records that this
map — unlike the skill-review-harness loader's, which
scripts/tests/text-capture-core-loader-sync.test.js checks against core's
exports — has no gate.

StandaloneSessionPicker.test.tsx's wiring test claimed to pin the wrapper's
import, but StandaloneSessionPicker.tsx is not in that suite's module graph:
the tests render SessionPicker, which takes `currentBranch` as a prop, so the
stub's only consumer is the test file's own import. Renamed the test and
corrected both comments to state what it pins and what it does not cover.

Comment and test-name only; no behavior change.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmto4amd1ku

* ci: pin the new core subpath exports step in the lint lane payload

`Check core subpath exports resolve` was added to the lint_and_static job
without being added to the pin that asserts that job's full-gated payload by
name and order, so the guard failed with 19 received against 18 expected.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-conflict/jmtosl3ualt

---------

Co-authored-by: Qwen-Coder <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
2026-09-06 11:21:50 +00:00
易良andQwen-Coder 133a1a39de ci: skip product tests for ECS updater maintenance (#10548)
* ci: classify ECS updater maintenance as CI-only

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): harden CI-only classifier trust

Co-authored-by: Qwen-Coder <[email protected]>

* ci: record ci.yml growth in the size baseline

The ci-only-profile wiring added real workflow content; combined with
prior main growth this pushed ci.yml 4853 bytes past its recorded
98043, over the 4096 allowance. Bump the ratchet to the current
102896 so the check reflects the reviewed growth.

Co-authored-by: Qwen-Coder <[email protected]>

* test(ci): gate the classifier trust test to the Linux lane

The fail-closed trust test drives the workflow step through `bash -c`
with a Unix ':'-joined PATH and an extensionless shebang `gh` stub,
which the Windows lane cannot express. Gate it the same way this
file's other bash-driven cases already do, keeping the Linux lane as
the authoritative coverage (scripts/tests/vitest.config.ts).

Co-authored-by: Qwen-Coder <[email protected]>

* ci: log the reason behind the classifier trust verdict

The permission lookup discarded all diagnostics (2>/dev/null || true)
and the log printed only the verdict, so an API outage, an empty
permission, and a real below-write permission all produced the
identical "PR classifier trusted: false" line. Record which branch
made the decision and print it alongside the verdict; the
can_trust_pr_classifier output and its fail-closed semantics are
unchanged. Bumps the size baseline for the added lines.

The trust test's helper now captures the step's stdout, and a new
Linux-gated case asserts the denial reasons stay distinguishable.

Co-authored-by: Qwen-Coder <[email protected]>

* test(ci): pin the remaining classifier trust reason branches

The R1-3 reason logging added six per-branch reason strings, but the
'not a pull_request event' and 'PR author missing from the event
payload' branches were unreachable from the tests: runClassifierTrust
hardcoded GITHUB_EVENT_NAME and PR_AUTHOR, so dropping or swapping
either assignment shipped with every test green. Give the helper
optional eventName / prAuthor overrides and extend the reason test to
assert both branches stay denied and distinguishable; the merge queue
exercises the first one on every merge_group run.

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): run PR classifier from trusted base

Compute the CI profile once from an immutable base-SHA checkout, then pass the validated output to product jobs without executing the PR head's classifier. All missing, failed, or unknown classifications continue to fall back to full CI.

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): prepare trusted classifier checkout

Clean shared-runner residue before checking out the trusted base classifier and update the workflow test fixture to mirror its isolated path.

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): sweep leftover trusted-ci-classifier before checkout

actions/checkout keeps an existing checkout directory when its origin
URL matches and runs `git checkout --detach` inside it without a
core.hooksPath mitigation, so a leftover trusted-ci-classifier from an
earlier classify_pr run on the shared ecs-qwen pool would execute
whatever hooks residue planted there — code execution through the
classifier checkout the sweep already defends. Add the path to the
pre-checkout sweep loop in every ci.yml copy (the byte-identity pin
keeps them in lockstep) and in qwen-code-pr-review.yml, per the
documented third-residue-name contract, and pin the removal
behaviorally.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmthmo4kw80

* test(ci): pin the shared-pool ownership restore step's existence

The ordering assertion alone survives removing 'Restore workspace
ownership': restoreIdx becomes -1 and cleanIdx > -1 still holds, while
root-owned leftovers from a containerised job defeat the sweep and the
checkout again — the EACCES incident class the step exists for. The
step is new in classify_pr on this branch, so pin it explicitly on
every shared-pool job.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmthmo4kw80

* fix(ci): pin the profile producer id and log the degraded fallback

Two silent degradations in the new profile wiring:

- The classify_pr job output resolves against step id ci_profile, but
  nothing pinned the producer step's id or its GITHUB_OUTPUT write: a
  rename empties the output and every consumer silently runs the full
  profile. Pin both on the producer step.
- When classify_pr fails (continue-on-error) the consumers coerce the
  empty output to full and log the exact same line as a legitimate full
  classification. Emit a ::warning breadcrumb when the producer output
  is empty, on both consumer copies, and pin it.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmthmo4kw80

* test(ci): pin the profile consumers' allowlist and output write

The consumer loop pinned every load-bearing line of both `Use trusted
CI profile` steps except the allowlist case line and the GITHUB_OUTPUT
write. If a later edit drops the echo in either consumer,
steps.ci_profile.outputs.ci_profile becomes empty and every downstream
profile gate mis-compares. Extend the pinning test to cover both
consumer copies of those two lines (mutation-probed on all four
consumer lines).

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmthzj3978p

* docs(ci): explain the third residue name in the review sweep comment

The sweep loop grew to three names (adding trusted-ci-classifier), but
the comment above it still said "those two known names". Update the
count and add the explanatory paragraph for the third name, mirroring
the ci.yml copy: the residue comes from ci.yml's classify_pr base-SHA
checkout on the shared pool, and actions/checkout would reuse it and
run git (hooks included) inside it.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmthzj3978p

* fix(ci): run only dependency-free helper tests in github_ci_only lane (#10548)

The github_ci_only helper step runs before any dependency install — the
setup-node and `npm ci` steps are gated on the full profile — but it ran
the entire HELPER_TESTS list, 8 of whose suites import the `yaml` npm
package. A PR classifying into this lane (e.g. a fork PR touching only
.github/workflows/update-ecs-runner-qwen.yml, which this PR admits to the
fast lane) failed closed with ERR_MODULE_NOT_FOUND on a fresh runner.

Split the list: the fast lane now runs HELPER_TESTS_DEP_FREE (suites whose
import closure is node: builtins only); HELPER_TESTS keeps the full list
for the full-profile helper step, and the merge queue's full run
re-checks what the fast lane skips. A pinning test in ci-platform-lanes
keeps the yaml suites out of the fast lane.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmtire6cja9

* fix(ci): drop yaml-dependent helper suite from dep-free test lane

web-shell-visuals-publish.test.mjs statically imports the `yaml` npm
package, but HELPER_TESTS_DEP_FREE runs before any dependency install
(npm ci is gated to the full profile), so the suite failed with
ERR_MODULE_NOT_FOUND on fresh runners (review feedback from qqqys).
Remove it from the dep-free lane only; the full-profile helper lane
still runs it.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmtj6easob2

* fix(ci): keep yaml-dependent visuals suite out of github_ci_only lane

web-shell-visuals-publish.test.mjs statically imports `yaml`, so the
dependency-free github_ci_only lane can never execute it. Listing it in
GITHUB_CI_ONLY_FILES meant a PR touching only that suite got every
required check green without the changed file running once, because the
full-profile helper step is gated on ci_profile == 'full'. Drop the
entry so such PRs keep the full profile, and pin the classification
with a test assertion (verified red with the entry, green without).

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmtjj99h9bs

* fix(ci): grant classify_pr pull-requests read scope and pin the fast-lane exclusion

F1: classify_pr had no job-level permissions block, so its token carried
only the workflow-level {checks: write, contents: read, statuses: write}
grant. The classifier wrapper calls repos/{}/pulls/{}/files and
repos/{}/pulls/{}, which 403 without pull-requests scope and fall back to
full CI. Grant contents: read + pull-requests: read on the job and pin the
block with an assertion in the no-ak integration test.

F7: the fast-lane pinning test enumerated 8 of the 9 yaml-importing suites;
add web-shell-visuals-publish.test.mjs (the suite the F6 commit excluded
from HELPER_TESTS_DEP_FREE) and fix the comment count, so re-adding it to
the fast lane fails the test.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmtjlef91bw

* ci: give the github_ci_only fast lane a single dependency-free owner

lint_and_static's 'GitHub CI helper checks' step ran the full HELPER_TESTS list under the github_ci_only profile, where every dependency install in that job is gated on ci_profile == 'full' - so the 9 suites that statically import `yaml` died with ERR_MODULE_NOT_FOUND on a fresh hosted runner. This branch newly routes ECS-updater and actionlint PRs into that profile, which made the fast lane deterministically red on exactly the flow it exists to serve.

Repoint that step at HELPER_TESTS_DEP_FREE and drop the duplicate copy in the `test` job: ci-platform-lanes.test.js reserves that step name for lint_and_static, so the copy also broke the shared-prelude byte-identity guard and the out-of-test pin. Select the fast lane by its gate rather than by the list it consumes, so a regression back to env.HELPER_TESTS cannot drop out of the filter.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmto8kxx1l2

* ci: keep the ECS updater workflow guard reachable from its own fast lane

Routing .github/workflows/update-ecs-runner-qwen.yml into GITHUB_CI_ONLY_FILES left its guard unreachable on exactly the PRs that change it. The 21-case vitest suite under scripts/tests/ only runs under the full profile — test:scripts is gated on ci_profile == 'full', and test_macos / test_windows fire only on merge_group|schedule|workflow_dispatch — while the dependency-free node:test replacement was a strict 7-case subset. Renaming the matrix job name template, which breaks the reporter script startswith(poolPrefix) filter, or dropping the issues:write permission from report_failure, merged with every check green.

Port the remaining 14 cases into the dep-free suite behind the same win32/jq capability gate the bash replays already used, and delete the vitest copy so the two cannot drift. The suite imports node: builtins only, so it stays inside the HELPER_TESTS_DEP_FREE contract. Verified locally: 21/21 pass, 0 skipped; renaming the matrix job name template reddens "filters the run jobs by the prefix the matrix job actually uses", and dropping issues:write reddens "reports a failed fleet update only when a pool actually failed".

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmto8kxx1l2

* style: apply prettier to the ported ECS updater guard suite

The ported assertions were hand-converted from vitest and drifted from the repo Prettier style, which the full-profile Run Prettier step would have flagged on this PR.

Co-authored-by: Qwen-Coder <[email protected]>
Patrol-Run: qwen-pr-closeout/jmto8kxx1l2

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-09-06 11:19:07 +00:00
e3d26283e6 ci: close the guard holes round 2 found in the lint lane (#10919)
* ci: close the guard holes round 2 found in the lint lane

Nine follow-up findings on #10756's guards, merged before the round posted; all confirmed, fixed here.

The serializer at the heart of the prelude-identity guard was wrong: JSON.stringify's array replacer is a property allowlist applied at every nesting level, so each step's nested with:/env: serialized as {} and the guard could not see the fields it claimed to guard — Checkout's ref/fetch-depth, setup-node's versions, and the TRUSTED_CI_PROFILE binding whose drift to a literal would skip every profile-gated step while reporting green. Replaced with a recursive canonicalizer. The divergence exemption is now the field, not the step: a whole-step exemption left the collector's uses: revision — the one action pin in the job — compared by nothing, so a retag to a mutable @v4 was invisible.

Order is now pinned too: name-keyed maps are order-blind, and the steps context reads '' for steps that have not run yet, so a profile producer parked below its consumers would evaluate every ci_profile == 'full' gate false and complete the lane green having executed nothing. And the payload itself — the twenty moved steps the job exists to run — was pinned by nothing at all: deleting Run Prettier, flipping its gate, or re-adding a copy to test all stayed green. An exact ordered-list assertion now holds the full-gated payload, keeps it out of test, and pins the two deliberately-different gates as different.

runs-on joins the pinned surface (whole-expression, equal to test's): pick_runner is the fork-trust boundary and a literal pool label array would bypass it whole while desyncing from the timeout expression that reads the same output. no-ak's trusted-profile consumer loop gains the third consumer.

Two fixes to the collector work itself: it moved to the job's last step — failure() is evaluated when a step is reached and never revisited, so at its old slot just after install it had already been passed, unfailed, before any lint step could die, and could only ever collect a prelude failure. And a failure-gated disk-state dump now precedes it: the install sampler dies with that step's trap EXIT, so the artifact otherwise held only install-window samples for a death in any of the 19 later steps. The dump appends; a bare > would truncate the window it exists to complement.

The lane's banner no longer overstates its case: the property that justifies the required-check role is deterministic outcome, not lightness — the bundle-closure step builds and bundles the CLI, the helper suites spawn subprocesses, and the pool contention the old text denied is exactly what the routed 90/45 ceiling prices. It also records the honest limit: the incidents that motivated the split failed in the unit-test step that stayed in test, and this lane would have been green on those merges.

All ten mutations red: nested fetch-depth drift, TRUSTED_CI_PROFILE literal, producer below install, payload deletion / gate flip / leak-back, collector retag, runs-on literal, collector at its old slot, dump removed.

* fix(ci): keep the failure dump when the disk is full, and pin four guard holes

Review round 1 found five holes in the guards this PR added. Four are fixed
here; the fifth is a comment that misstated its own mirror.

The state-at-failure dump wrote its only copy into the samples file — the very
file whose writability the dump exists to investigate. Under ENOSPC the
redirect failed, `2>/dev/null` and `|| true` swallowed the failure and its
status, and the step exited 0 having recorded nothing, so oncall could not tell
"dumped, nothing interesting" from "could not write". Mirror through `tee -a`,
which still emits the data to stdout when the file write fails and reports why
on a stderr the block does not swallow. An executed case occupies the target
path with a directory and asserts the dump survives in stdout while the step
stays green.

The other three are pins that did not bite:

- The shared-prelude floor stayed at 12 after the divergence exemption narrowed
  and re-admitted the collector, making the intersection 13. A floor below the
  real count lets any one shared step be deleted out of either job unnoticed.
- The serializer at the heart of this PR had no fixture. Both sides of every
  comparison flow through it, so regressing the call site to the pre-PR array
  replacer compares {} to {} and the drift loop reports green — the exact blind
  spot the recursion replaced. Pin a known nested key on the serialized OUTPUT
  rather than on the serializer alone, because the regression that matters is a
  call site swapped back to an allowlist.
- Nothing pinned the `needs` edge that makes every classifier output this suite
  reads non-empty at runtime; deleting it decouples the lane silently, dropping
  runs-on to hosted and taking the degraded empty→full profile fallback.

And the collector's comment cited `test` as its mirror for lastness while
`test`'s own collector sits four steps from the end, so the hole this PR closes
here stays open there. Correct the comment and leave that job to a follow-up
rather than widen this change into a second job.

Mutations measured red: needs edge deleted; a shared prelude step deleted; the
serializer call site regressed to the array-replacer form; tee reverted to a
bare redirect, which reds both the pin and the executed case.

* ci: restore the blank line separating lint_and_static from web_shell_e2e_smoke

Appending the failure dump and the collector after the pre-existing blank
line left the job ending flush against the next job key -- the only one of
ci.yml's eight job boundaries with no separator. Semantically inert: the
parsed workflow hashes identically before and after.

* ci: correct the HELPER_TESTS file count in the lint lane comment

The comment justifying `lint_and_static` as a required check argues the lane
is not light, citing "the 23-file HELPER_TESTS battery". The env list at
ci.yml:82 enumerates 24 files and is byte-identical to main's, so the count
was wrong on arrival -- in the one paragraph that says it exists so the
required-check follow-up is argued "on true premises".

Same class as the collector comment 2082e26 corrected. Byte-neutral: no line
count, no parsed YAML, no gate. Nothing pins comment prose -- scripts/tests
and .github/scripts assert `ci.env.HELPER_TESTS`, not the banner -- so there
is no test to mutate; the count was measured two ways instead (token count of
the env value, and a diff of the main vs HEAD file lists, which are
identical). The sibling count in the dump comment, "19 substantive steps
after install", was checked at the same time and is correct: 22 steps follow
install, minus the always()-gated cache report and the dump and collector
themselves.

---------

Co-authored-by: 易良 <[email protected]>
Co-authored-by: qwen-code-dev-bot <[email protected]>
Co-authored-by: qwen-code-dev-bot <[email protected]>
2026-09-05 03:01:42 +00:00
Shaojin Wen a8248eaed3 ci: collect coverage only on the post-merge run and run cli tests under node (#10890)
Pull-request runs collected v8 coverage for cli, core, web-shell and
vscode-ide-companion on every push, uploaded it, and fed it to a coverage
comment that failed about as often as it posted; nothing else read the
reports. Measured on the cli suite, instrumentation plus the per-file merge
cost about a fifth of the wall time. Coverage is now switched on by
QWEN_CI_COVERAGE, which the unit-test step sets only for push (main); the
upload gate follows the switch, and the comment job and its composite action
go. The two workspaces that forced --coverage from test:ci rely on the same
switch.

The cli package also ran every test file under jsdom although only 90 of the
1002 touch a document. The default is now node; those 90 files carry a
`@vitest-environment jsdom` control comment. Together the two changes take
the cli suite from 410 s to 290 s on an idle 6-worker host.
2026-09-03 08:49:43 +00:00
Shaojin Wenand易良 b8f35aa5d3 ci: split lint and static checks out of the Test job (#10756)
* ci: split lint and static checks out of the Test job

The `test` job bundled two halves with opposite reliability profiles: twenty deterministic lint/static steps (ESLint, actionlint, shellcheck, yamllint, Prettier, schema/notices freshness, lockfile, workspace isolation, TUI dep direction, bundle closure, helper tests, workflow size) and the unit-test run, which was measured failing 41% of post-merge runs on flake alone this week. main still has zero required status checks — a duplicate-declaration compile error shipped to main on 09-01 and sat red for half an hour precisely because nothing gates merges — but the combined job could never become required while the flaky half holds the deterministic half hostage.

Move the twenty static steps into a new `lint_and_static` job with the same event surface, the same runner routing, the same release-sync convention (run on skip_ci PRs, no-op per step, so a required context still reports), and its own copy of the checkout/node/install prelude plus the CI-profile classifier (the classifier is one gh api call; duplicating it costs seconds and keeps `test`'s `ci_profile` output — which web_shell_e2e_smoke consumes — untouched).

The surgery is a pure move, proven at YAML level: the old job's 40 steps equal the new jobs' union exactly, every step is field-for-field identical to its original, every other job is byte-identical, and `lint_and_static.if` equals `test.if` — pinned by a new equality assertion that rides on test's whole-literal pin, so the two gates cannot diverge silently. Event-matrix A/B against main shows exactly one new row: lint_and_static running on push, pull_request, merge_group and workflow_dispatch, and not on schedule. Mutation-checked: a lint gate that silently drops push, or silently joins the nightly, each reddens two assertions.

Scope disclosure: this lane could NOT have caught the 09-01 incident — that was an esbuild transform error inside a test file, ESLint accepts the duplicate binding (verified against the actual broken file), and web-shell's tsconfig excludes *.test.tsx from typecheck. Catching that class needs the unit-test lane, which can only become required once its flake rate is fixed. This split makes the half that CAN be required independent of that work.

The size-baseline bump records the duplicated prelude comments; the growth is the split itself.

* ci: give lint_and_static a cold-cache-sized timeout

The first real run of the new lane was beheaded at exactly 30 minutes: a cold-cache pool runner paid 892s for npm install where the sibling Test job on the same run paid 347s, ESLint and Prettier both ran at ~2x their historical timings, and the job died mid bundle-closure. The 13-15 minute prediction in the split commit came from warm-cache step timings and did not price the first run on any given runner. 45 matches desktop_shell and still surfaces a wedged runner within the hour.

* ci: give lint_and_static the contracts its template job already had

Nine review findings, one root cause: the split copied test's behavior without copying its guards — every contract test that pins test's checkout, recovery, telemetry, budget and token surface enumerated jobs by name and never reached the copy.

Contract extensions: the checkout-ref whole-literal pin now loops both jobs; no-ak-integration-ci's guardCalls gains a lint_and_static entry pinned to test's event-aware expected_sha shape; qwen-triage's unconditional-ownership-restore suite gains the new job; ci-disk-pressure pins the lane's sampler AND its collector — the collector was simply missing (the lane produced the #10035 telemetry and destroyed it with the runner temp dir on the exact ENOSPC death the sampler explains), now added with a per-job artifact name since upload-artifact v4+ rejects duplicates when both jobs fail in one run. New pins beside the gate-equality test: timeout stays the measured 45 (30 was the beheading value), and permissions stays exactly {contents: read} — the workflow grants checks/statuses write, and this lane runs contributor lifecycle scripts.

Fidelity fix: the size gate moved back above the dependency install, the fail-fast position it deliberately held in the pre-split job — after npm ci it delivered its verdict ~6 minutes late warm, ~15 cold, and hid size violations behind unrelated install failures. An ordering assertion in workflow-size.test.js pins the position.

Drift guard: the merge that landed mid-review proved the copies rot — #10799's trusted-classifier rework updated test's prelude and left the lint copy stale, resolved by hand. A shared-prelude equality test now holds every same-named step field-for-field identical (sole deliberate divergence: the collector's artifact name), so the next drift is a red test, not review archaeology.

Prose: the three stale two-job narratives (test's header, HELPER_TESTS, the lanes test file) now describe the three-job reality.

All nine mutations red: push-arm drop, verify-head drop, gate reordering, collector drop, ownership-restore drop, timeout lowered to 30, permissions deleted, permissions widened, one-character prelude drift.

* ci: route lint_and_static's timeout like its siblings

The flat 45 beheaded a second honest run: on cold-cache hk3 pool runners every step lands 2-4x its warm timing (install 940s vs 347s, bundle closure 681s vs 171s) and the serial sum cleared 45 minutes with no step wedged. The pool contention that sized test at 120/60 and no_ak at 60/30 applies here identically; a bare constant priced only the warm half of that reality. ECS 90 / hosted 45, same expression shape as test's, pins updated with both measurements.

---------

Co-authored-by: 易良 <[email protected]>
2026-09-03 03:53:57 +00:00
1e695c34ff fix(ci): retain disk samples when npm install fails (#10517)
* ci: gate heavy jobs on a disk floor and persist pressure samples

The in-repo slice of #10035: fail fast on a saturated self-hosted host
before npm ci instead of dying on ENOSPC mid-run, and keep the
disk-pressure timeline from a failed run as an artifact so the peak can
be correlated with the job and runner after cleanup reclaims the host.

* ci: single-quote the failure() condition to satisfy yamllint quoted-strings

Co-authored-by: Qwen-Coder <qwen-coder @alibabacloud.com>

* ci: keep TMPDIR routing block byte-identical across test legs

The sample-file setup landed inside the routing block that
no-ak-integration-ci pins identical across test/test_macos/test_windows,
breaking the identity assertion. Move the DISK_SAMPLES definition and
DISKCONTEXT header ahead of the routing block; the DFSAMPLE lines still
carry the routed tmpdir.

* fix(ci): validate disk floor overrides

* fix(ci): reject oversized disk floor overrides

Keep the disk-floor shell comparisons inside bash's signed integer range by rejecting numeric overrides that cannot be compared safely. Add the existing helper test coverage for the overflow boundary.\n\nCo-authored-by: Qwen-Coder <[email protected]>

* test(ci): pin the disk-floor overflow guard boundaries

Pin the three behaviours of validate_floor_override that the current
suite leaves unpinned, each verified to catch its mutant:

- INT64_MAX is a legal floor and must reach the disk comparison
  (tightening the length check to -gt 18 now goes red).
- Zero-padded overrides padded past 19 raw characters are normalized
  and accepted (removing the leading-zero strip now goes red).
- 20-digit values are rejected by the length branch, which the
  existing 19-digit case never exercises (widening to -gt 20 now
  goes red).

Co-authored-by: Qwen-Coder <[email protected]>

* refactor(ci): simplify disk floor validation and sampling

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): preserve the TMPDIR sampler sentinel

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): retain disk samples from dependency install

Co-authored-by: Qwen-Coder <[email protected]>

* refactor(ci): reuse the workflow parser in disk tests

Co-authored-by: Qwen-Coder <[email protected]>

* chore(ci): trigger checks after retargeting to main

Co-authored-by: Qwen-Coder <[email protected]>

* test(ci): pin disk-pressure workflow contract

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): harden install disk sampling

Keep the install sampler non-fatal regardless of command-substitution order and satisfy the regex lint rule in its contract test.

Co-authored-by: Qwen-Coder <[email protected]>

* ci: record ci.yml growth in the size baseline

The disk-floor and disk-sampling legs added ~1 KB of real workflow
content; combined with prior main growth this pushed ci.yml 4848 bytes
past its recorded 98043, over the 4096 allowance. Bump the ratchet to
the current 102891 so the check reflects the reviewed growth.

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: Qwen-Coder <qwen-coder @alibabacloud.com>
Co-authored-by: Qwen-Coder <[email protected]>
2026-08-31 12:29:27 +00:00
易良andQwen-Coder 3da6d3b4e2 ci: allow full test jobs to finish on ECS runners (#10558)
* ci: allow full test job to finish on ECS runners

Co-authored-by: Qwen-Coder <[email protected]>

* ci: extend full test timeout for slow ECS runs

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-08-31 12:18:44 +00:00
易良andQwen-Coder 3aa1b14624 ci: stabilize tests under shared ECS host contention (#10552)
* ci: tolerate shared runner scheduling delays

Co-authored-by: Qwen-Coder <[email protected]>

* test(ci): inherit ECS contention budgets

Co-authored-by: Qwen-Coder <[email protected]>

* test(cli): extend PTY request budget on ECS

Co-authored-by: Qwen-Coder <[email protected]>

* test(cli): synchronize ACP archive race

Co-authored-by: Qwen-Coder <[email protected]>

* test(ci): isolate visuals publisher fixture modules

Co-authored-by: Qwen-Coder <[email protected]>

* test(ci): cover hostile parent package scopes

Co-authored-by: Qwen-Coder <[email protected]>

* test(sdk-java): isolate finite SSE fixture connections

Co-authored-by: Qwen-Coder <[email protected]>

* test(cli): make timer-range deadline deterministic

Co-authored-by: Qwen-Coder <[email protected]>

* test(sdk-java): tolerate shared CI scheduling delays

Co-authored-by: Qwen-Coder <[email protected]>

* test(sdk-java): preserve timeout contract on CI

Co-authored-by: Qwen-Coder <[email protected]>

* test(sdk-java): include request submission in timeout bound

Co-authored-by: Qwen-Coder <[email protected]>

* ci: extend no-AK shared runner timeout

Co-authored-by: Qwen-Coder <[email protected]>

* test(ci): guard no-AK integration budget

Co-authored-by: Qwen-Coder <[email protected]>

* ci(sdk-java): serialize ECS test execution

Co-authored-by: Qwen-Coder <[email protected]>

* test(sdk-java): relax stalled-response setup on CI

Co-authored-by: Qwen-Coder <[email protected]>

* test(ci): align no-AK timeout contract

Co-authored-by: Qwen-Coder <[email protected]>

* ci: extend Serve A/B shared runner budget

Co-authored-by: Qwen-Coder <[email protected]>

* ci: scope extended ceilings to ECS routing

The extended job ceilings (test 60->90, integration_no_ak 30->60) were
unconditional even though runs-on falls back to GitHub-hosted
ubuntu-latest for fork PRs from untrusted authors and when
MAINTAINER_ECS_RUNNER_DISABLED=true. Derive each ceiling from the
runner classify_pr selected so hosted paths keep the pre-contention
bound and a genuine hang there does not burn the extra 30 minutes.

The lane contract now evaluates the real timeout expression for both
routings instead of pinning a bare number, matching the
substitute-then-evaluate technique ci-runner-routing.test.mjs uses on
runs-on.

Co-authored-by: Qwen-Coder <[email protected]>

* fix(ci): address closeout review feedback

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: Qwen-Coder <[email protected]>
2026-08-30 14:38:54 +00:00
Shaojin Wen a268b7d175 ci: restore the post-merge push trigger on main (#10345)
* ci: restore the post-merge push trigger on main

`ci.yml` dropped its `push` trigger on the premise that the merge queue validates the merged tree before it lands. No merge queue is enabled — no queue run since 2026-07-02 — so nothing validated that tree on either side of the merge: `main` had no gate before a merge, and no check faster than the ~40-minute E2E after one.

Accept `push` on `main` in the `test` job alone. Every other job keeps its existing gate, so a post-merge run is exactly Classify PR + Test, and a nightly still resolves to exactly the two platform lanes. The expensive, PR-shaped steps inside `test` — the no-AK integration gate, the checkout-head verifier and the test reporter — already carry their own pull_request / merge_group gates, so the push lane is lint, static analysis and unit tests.

Route the lane to the ECS pool: a push to `main` is the most trusted context there is, and Test is the ten-plus-minute job that should not spend a scarce hosted Linux runner on every merge. classify_pr's own `runs-on` keeps no push arm by design — it is the canonical association-routing text that sdk-java.yml and serve-ab.yml mirror, and a push carries no author association to route on — so the two halves disagree on push deliberately; a new routing test pins that asymmetry so a later "drift fix" has to read the reason first.

* ci: address review findings on the post-merge push lane

Eight findings from the review pass, all confirmed against the tree.

R1-1 (Critical): the `test` job's checkout `ref:` fell through to `github.ref` on push — `refs/heads/main` resolved at fetch time, a moving tip — while the check run attaches to `github.sha`. The lane could validate a tree it did not report on, and nothing detected it: the checkout-head verifier is gated to pull_request / merge_group and its ancestor check passes for a newer tip regardless. Regressions would still be caught (the tested tree is a descendant), but attribution breaks — red on a clean commit, autofix filed against the wrong merge. Pin push runs to `github.sha`, mirroring the merge_group arm; `e2e.yml` gets the same guarantee from actions/checkout's default. The five other checkout copies sit in jobs that exclude push.

R1-5: the concurrency group was keyed on ref alone, so a post-merge run shared `Qwen Code CI-refs/heads/main` with the nightly (60-minute lanes, neither cancelling on main) and with any PR from a fork branch named literally `refs/heads/main` — a legal ref name that makes the group byte-identical while that run cancels in progress. Scope the group by event, as `e2e.yml` already does.

R1-6: `WORKFLOW_SIZE_BASE_SHA` was empty on push, so both size enforcers failed closed there while PRs stayed green on leniency — the next stale baseline would redden every merge with an error naming a PR that does not exist. Add `github.event.before`; `github.sha` stays rejected because workflow_dispatch would compare a file against itself.

R1-4: the coverage upload ran on push although its only consumer, post_coverage_comment, is gated to pull_request and nothing else reads the artifact.

R1-2: the push lane's YAML wiring had no test witness — the routing tests drive pick_runner's shell directly and stay green if the trigger or either job gate loses its push arm. Add assertions covering the trigger, both accepting jobs, the exclusivity of every other job, and the three fixes above. Each is mutation-checked: removing any one fix reddens exactly one test.

R1-3, R1-7 and R1-8 are three comments the trigger falsified: the failure watcher's "SCHEDULED run only" header, af-025's "ci.yml has no push trigger" premise, and this PR's own "only `test` accepts this trigger", which contradicted the classify_pr arm added in the same diff.

The added prose put ci.yml over its size baseline; trimmed back under the allowance rather than raising the baseline.

* ci: harden the push-lane guards after round-2 review

Five follow-up findings, all on the guards added in the previous commit rather than on the lane itself.

R1-2 (fix-induced): `test`'s push admission was pinned only negatively — no `!= 'push'` — so rewriting `test.if` into allowlist form drops push while every guard stays green: the exclusivity helper skips `test` because it is an expected push job, and the routing tests inject EVENT_NAME=push straight into pick_runner's shell, bypassing the job gate entirely. On a real merge the run would conclude `success` with the lane silently gone, and the failure watcher — gated on `conclusion == 'failure'` — would file nothing. Require the gate to stay exclusion-shaped.

R2-1: the push exclusivity loop was a hand-copy of the nightly one and the two had already diverged — the mirror's allowlist gained `schedule` while the original never gained `push` — so a push-only job would fail the nightly assertion with a message naming the wrong event. Replaced both with one parameterized helper, which also restores the `not.toBe('')` assertion the copy dropped.

R2-2: the concurrency assertion pinned only the added `github.event_name` token. Flipping `cancel-in-progress` to `true` was measured invisible to all 1854 test:scripts tests, while consecutive merges would then cancel each other — the history this repo already recorded once, when 67 of 100 push runs were cancelled and only 25 reported. Pin all three components, the way e2e-workflow.test.js pins its own.

R2-3: the group's pull_request half was still keyed on `head_ref` alone, so same-named branches across forks — and a fork's default branch is `main`, the commonest head_ref there is — share one group and cancel each other's in-progress runs. Add the source-repo term, matching security-checks.yml and serve-ab.yml. Supersede within a PR is preserved: one branch backs at most one open PR per fork.

R2-4: the trigger was pinned by `branches` only; a sibling `tags:` key would widen the lane while the suite stayed green. On a tag-creation push `github.event.before` is all zeros, so WORKFLOW_SIZE_BASE_SHA is unresolvable and both size enforcers take their strict arm, while the failure watcher's `head_branch == 'main'` filter files nothing — a silent red on release day. Pin the whole trigger object.

Mutation-checked: each of the five reddens exactly the intended assertion, and the extracted helper now reports the correct event for a push-only job (verified by injecting a probe job — the nightly assertion passes, the push one fails naming push).

* ci: decide push-lane gates by evaluation, pin them by literal

Round 3 raised seven findings, all Suggestion, and they are all instances of one root cause the review itself named: the guards asserted on substrings, and a substring cannot see a connective. Rather than adding one more substring per mutation, this replaces the two mechanisms behind them.

Gate reachability is now decided by EVALUATING each job's `if` for the event instead of looking for tokens in it — the substitute-then-evaluate technique `.github/scripts/ci-runner-routing.test.mjs` already uses on `runs-on`. That closes R2-1's connective blindness (an `&&`→`||` flip inside an excluded job; an allowlisted equality paired with an event-neutral disjunct under `||`) without the recursive boolean parser the suggestion proposed: a hand-written expression parser is a new fail-open surface of its own, whereas evaluation computes the answer rather than pattern-matching the question. Any context term the substitutions do not model now throws instead of reading as excluded. The helper also asserts the positive direction — that the jobs which must run on an event do — which the token version never did, and which is precisely the "a lane nobody is told about" failure this file's header says it exists to prevent.

Every security-relevant gate is pinned as a whole literal rather than by substring: `classify_pr.if`, `test.if`, the checkout `ref:` expression, the coverage step's `if`, and both halves of the concurrency block. One shape closes R3-3, R1-2, R3-4 and R2-2 together — connector flips, added exclusions, deleted clauses and reordering all change the literal.

R3-2: the trigger comment claimed the test reporter is restricted to pull_request / merge_group. It carries no event gate at all; it is excluded by a same-repo clause that also keeps fork PRs out, so normalising it to the documented allowlist would run dorny/test-reporter on fork events that lack `checks: write`. Reworded per step, with that trap named.

R3-1: bump `.size-baseline` for ci.yml to 85336. The previous two rounds trimmed comments to stay under the allowance, which left 62 bytes of headroom and charged the next PR for growth this one introduced. The baseline file's own header says growth is deliberate and asks for the number to be bumped with a reason — the growth here is the per-finding rationale the review asked for, so it is recorded rather than squeezed out.

Mutation matrix across all three rounds: 22 mutations, none escaping — the seven this round probed as surviving now redden, and the twelve from rounds 1 and 2 still do.
2026-08-28 23:06:40 +00:00
易良 385d7cbbda ci: serialize helper test files (#10371)
* ci: serialize helper test files

* test(ci): pin helper test serialization
2026-08-28 08:46:24 +00:00
Shaojin Wen 0756be0ce7 ci: take the macOS and Windows lanes off pull requests (#10059)
* ci: take the macOS and Windows lanes off pull requests

Both lanes were costing contributors more than they were telling them.
Measured over the 18 hours before this change, on pull requests:

  - Windows reported 13 failures and 0 successes. The failures are one
    standing set of Windows-only path and symlink cases — resolved paths
    into read_many_files, releaseWorktree through an ancestor symlink,
    the SHA-256 review worktree — repeating across unrelated PRs, so the
    red X almost never belonged to the diff under it.
  - macOS queued for a p90 of 42 minutes and up to 159, on a hosted pool
    this repository does not saturate by itself: 20 of the 63 sampled
    waits happened with zero macOS jobs of ours running.

Neither lane gates a merge — the `main` ruleset carries no required
status check — so none of that waiting or noise was buying protection.
Leave them on the nightly, the merge queue and dispatch, and drop the
pull-request arm plus the classifier job that existed only to feed it.

That makes the nightly load-bearing rather than a backstop: it is now
the only report of a non-Linux regression, so add a guard for the three
ways it could go quiet — the schedule disappearing, a lane no longer
accepting it, or a third job joining it and failing the run for reasons
that have nothing to do with either platform.

The classifier, its script mode and both test files are left in place so
restoring the pull-request trigger, once the Windows failures are fixed,
is a revert plus two `if` arms.

* ci: pin the retired-classifier contract in ci-platform-lanes.test.js

The lane change left scripts/tests/ci-platform-lanes.test.js pinning the
shape it removed — a sensitive-PR trigger arm and a live classify_platform
job — which failed the Test job on this branch. Rewrite the suite to pin
the new contract instead: both lanes run on the schedule, the queue and
dispatch and on nothing else; the pull-request arm and the classifier are
gone whole (a half-restoration — a trigger without its classifier, or the
reverse — fails); the classifier's own scripts stay tested so restoring
the trigger stays a clean revert; the nightly still reaches exactly the
two lanes and its failure still files an issue.

That suite already owned the nightly-liveness assertions, so the
platform-lane-triggers.test.mjs guard added earlier on this branch
duplicated it — dropped, along with its HELPER_TESTS entry.

Verified by mutation: deleting the schedule, dropping schedule from one
lane, letting a third job onto the nightly, and reintroducing the
pull-request arm without its classifier each fail the suite; the branch
shape passes 17/17.
2026-08-26 03:31:55 +00:00
519e824dc7 fix(ci): give the macOS and Windows lanes a trigger again (#9370)
* fix(ci): give the macOS and Windows lanes a trigger again

Both lanes are gated on `merge_group`, and no merge queue is enabled on
this repository — the `main` ruleset carries only deletion,
non-fast-forward and pull_request rules, no status check is required,
and merges land as squashes. The last `merge_group` run of anything was
2026-07-02. So the gate was an off switch: the lanes reported as
"skipped" on every pull request, which reads as agreement, and nothing
ever reached them afterwards. The only signal this repository has about
a host that is not Linux with a GNU userland had been silently off for
six weeks, which is how #9220 shipped a GNU-only `realpath -m` in a
workflow guard with the suite that pinned it red on every Mac.

Three triggers now, in cost order.

A pull request whose diff a new classifier recognises as
platform-sensitive: shell scripts of every dialect, workflow and
composite-action YAML and the scripts they call, the script layer and
its tests, the test-runner configuration that decides which suites run
where, the root manifests, and source paths whose segments name a
platform-coupled subsystem. It is a net, not a proof — it cannot see a
platform assumption inside an ordinary source file, and no path rule
ever will — so every unknown answers "sensitive": an unreadable listing,
an unparsable entry, a fork pull request, a truncated file list, or the
classify job failing outright all end as "run the lanes". Only a
confident `false` skips them.

The merge queue, if it is ever enabled again, unchanged.

And a nightly run on `main` for everything the path list cannot see.
Every other job in the workflow excludes `schedule` explicitly, so a
nightly is exactly two jobs, and 'Qwen Code CI' joins the workflows the
main-failure watcher opens autofix issues for — a red lane nobody is
told about is the same silence the queue gate produced. That watcher
gains a trigger-level `branches: ['main']` filter so the CI workflow's
pull-request completions do not raise an event there just to skip.

The classifier runs in its own small hosted job rather than as a step in
`classify_pr`: that job's outputs pick the Linux runner for the whole
run, and this one needs a checkout — on a pool whose workspace other
jobs have poisoned before. It checks out the pull request's BASE commit,
never the head: it runs before any review and executes a script from the
tree it checks out. Its listing goes through the existing
classify-pr-profile.sh wrapper, extended with a mode argument, because
that wrapper's whole point is that one PR is never listed twice and
classified differently in two places.

Twelve tests pin the wiring — the triggers, the fail-safe direction of
the gate, the base-commit checkout, that a nightly stays two jobs, and
the alerting — and ten more pin the classifier itself, including the
substring traps (`Shellfish.tsx`, `cryptic.ts`, `plateauDetector.ts`
must not drag both lanes in) and every fail-safe path. Mutation-checked:
flipping the gate to `== 'true'`, dropping the schedule from a lane,
dropping the nightly guard from the ubuntu job, pointing the checkout at
the head, dropping the classifier's test from the helper list, and
removing the schedule trigger are each caught.

* fix(ci): gate the Windows lane's checkout verification per trigger

The first thing the revived triggers hit was not a test failure but the
lane's own plumbing. `test_windows` verifies its checkout with
`verify-checkout-head`, and the input was written when this lane ran in
the merge queue alone: `expected_sha: github.event.merge_group.head_sha`,
with no event gate. On a pull request that expression is empty, the
action refuses an empty SHA, and the lane went red in 63 seconds without
running a test — the first Windows run in six weeks, failing on the
trigger rather than on the code.

Give it the event-aware shape the Ubuntu gate already uses, and skip it
where there is nothing to verify: the scheduled and dispatch runs check
out a branch by name, not a head commit.

Pinned generally rather than by name: for both lanes, any step whose
inputs read a `github.event.<event>` context must be gated to that
event, in the step's own `if` or in the expression itself. Restoring the
old spelling turns that test red.

* fix(ci): close review round on the revived platform lanes (#9370)

* fix(ci): pin the Windows lane routing to the canonical trust policy (#9370)

* fix(ci): host-probe the macOS lane's bash and GNU dependencies (#9370)

The revived macOS lane ran the shared suite for the first time since the
merge queue went dark and failed on exactly three tests, identical across
three consecutive runs: the bite-check block and the baseline A/B green
path die with `mapfile: command not found` (a bash >= 4.4 builtin; macOS
ships 3.2), and the health-probe repair case trips over BSD `mktemp -u`
attempting to create where GNU's print-only `-u` just names the canary.

Both suites pin scripts that only ever execute on Linux runners —
ubuntu-latest or the Linux ECS pool — so the defects cannot exist in
production; the suites just cannot run those scripts on a macOS host.
Follow the convention #9220's fix established for the realpath case in
the same file: probe the host capability, not the platform, and skipIf
the three dependent tests where it is absent. A Mac with a newer bash or
GNU coreutils fronting PATH keeps the coverage; the Linux lane runs all
three unconditionally. The large bite-check test body is re-indented by
prettier around the new skipIf wrapper; no other token changed.

* fix(ci): stop the subsystem rule matching compounds that name something else

The platform-sensitivity classifier split a path segment on dashes and
underscores anywhere, so `packages/web-shell/**` matched the `shell`
keyword — one of this repository's largest packages, a browser UI with
no host coupling, summoning both expensive lanes on every change to it.
That is the cost the gate exists to avoid, spent on the wrong diffs.

A keyword now counts when it NAMES the thing: a whole path segment
(`src/sandbox/**`, `platform/paths.ts`, `shell.ts`) or the head of a
hyphen/underscore stem (`pty-host.ts`). Not a trailing part of a
compound, which belongs to whatever the leading word names, and still
not a substring inside a longer word.

Pinned both directions, including a directory that IS named for the
subsystem wherever it sits (`web-shell/components/shell/**` stays
sensitive). Mutation-checked: dropping either rule, or restoring the
split-anywhere spelling, turns the suite red.

Also drops a wrong issue citation in the watcher test's comment: the
nightly comes from this change, not from the wipe-guard back-port.

* test(ci): pin the gate as a disjunction and the watcher's name binding

Two mutation-survivable gaps in this PR's own tests.

The clause-presence assertions left a connective mutation alive: `||` →
`&&` between two event clauses keeps every asserted string in place and
makes the gate unsatisfiable for every trigger — both lanes silently off
again, which is the state this PR exists to end. Read the event group and
require it to be a disjunction, allowing `&&` only inside the
pull-request clause that binds to the classifier output.

And the watcher's binding to this workflow is by display name:
`workflow_run.workflows` matches the watched workflow's `name:`, so
renaming ci.yml unhooks the nightly's alerting silently. Pin both sides.

Both checked by mutation: flipping one `||` and renaming the workflow
each turn a named test red.

* fix(ci): close the classifier's CRLF gap and widen the lane step scan

Three findings from this round, all in the direction of the tests and
the parser being less clever than they claimed.

The classifier's JSONL reader split on `\n` while its sibling splits on
`/\r?\n/`. Every suffix rule here is end-anchored, so one carriage
return on a CRLF listing would leave `build.sh\r` and classify a
script-layer change as ordinary source. Matched to the sibling, with a
fixture on both the JSON and the raw-line path.

The per-trigger step scan serialized only `with:` inputs, so the same
defect wearing an `env:` or `run:` key escaped it; it now reads all
three.

And the nightly blast-radius guard tested for the MENTION of an
allowlisted event rather than the IMPOSSIBILITY of `schedule` — a job
gated `pull_request || schedule` satisfied it while running every night.
It now requires the absence of an explicit schedule clause too.

* style(ci): format the runner-routing suite

The Windows trust-policy matrix added last round left the file outside
prettier's style, which the repository's lint step fails on; main's copy
is clean. Formatting only — the nine assertions are unchanged and still
pass.

* fix(ci): gate the two mapfile-crossing gate tests on the host probe (#9370)

* fix(ci): keep pull requests off the persistent Windows pool and cover native audio (#9370)

A pull_request run executes the workflow YAML from the PR's own merge
commit, so the test_windows runs-on trust clause it evaluated could be
rewritten by any PR the lane admits. Every pull request now runs on
hosted windows-2022 unconditionally; the pool is reached only by the
post-approval merge queue, schedule and dispatch, guarded by the
kill-switch. The routing tests and the exact-line pin are re-pointed at
that enforceable shape.

Also add audio to the platform-sensitivity classifier's subsystem
keywords: packages/audio-capture is a node-gyp workspace compiled
per-host on exactly the two revived lanes, but its native sources
(.cc/.mm/.gyp) carried no rule and a PR touching only them skipped both
lanes. The workspace directory now classifies sensitive; an ordinary
.cc elsewhere stays ordinary source.

Co-authored-by: Qwen-Coder <[email protected]>

---------

Co-authored-by: qwen-code-dev-bot <[email protected]>
Co-authored-by: qwen-code-dev-bot <[email protected]>
Co-authored-by: Qwen-Coder <[email protected]>
2026-08-25 11:53:05 +00:00