Commit graph

246 commits

Author SHA1 Message Date
rockybot2026
732f4d8a29
feat(voice): support trusted private ASR base URLs (#8350)
* feat(voice): support trusted private ASR base URLs

* fix(voice): address private endpoint review findings

* test(voice): cover private endpoint edge cases

* test(voice): pin remaining endpoint edge cases

* fix(voice): address private endpoint review feedback

* fix(voice): clarify allowlist URL and normalize IPv6

* fix(voice): harden NAT64 address validation

* fix(voice): address managed endpoint review findings

* refactor(voice): extract shared IPv6 transition unwrap ladder (#8350)

Deduplicate the IPv6-transition unwrapping sequence (mapped, compatible,
NAT64, dotted-quad) that was repeated verbatim between isPrivateNetworkIp
and isAlwaysBlockedVoiceAddress on both CLI and Desktop surfaces. A single
unwrapIpv6TransitionStep helper now yields the next canonical address (or
'blocked' for unrecognized ::ffff: forms), and each predicate recurses
through it, preserving the exact re-check semantics at every unwrap level.

* test(voice): cover allowInsecureBaseUrl wiring through desktop default transports (#8350)

* fix(voice): add allowlist hint to private-network rejection error (#8350)

* fix(voice): reject always-blocked base URLs before offering the allowlist hint (#8350)

* fix(voice): resolve exact desktop voice provider before OAuth (#8350)

* fix(voice): address review feedback for trusted private base URLs (#8350)

* fix(voice): align desktop voice resolution with CLI semantics (#8350)

* fix(voice): scope desktop fail-closed resolution to policy-bearing entries (#8350)

* fix(voice): address round-8 review findings for trusted private base URLs (#8350)

Run the invasive process-global `mock.module('ws')` suite as
voice-ws-handler.isolated.ts so the desktop package's single-process
`bun test` run no longer leaks the fake socket into unrelated ws
consumers; the existing isolated loop runs it in its own process.

Shape-guard the desktop provider scan: non-object modelProviders
elements are skipped (falling through to OAuth instead of throwing a
raw TypeError), and non-string baseUrl/envKey/settings.env values on a
voice-model entry now surface the PROVIDER_ENTRY_REMEDY remediation
error instead of crashing.

Compute the DashScope-compatible /v1 rewrite before any allowlist
match in fromExactModelProvider so the stage-1 check, the remediation
messages, and the top-level recheck all compare the same final URL and
a single allowlist entry converges for split-horizon deployments.

Extend the CLI allowlist remediation messages to state which settings
scopes honor the entry, since serve mode never shows the interactive
workspace-strip warning. Thread providerProtocol through the CLI voice
model seams (createVoiceModelSource and the daemon buildModelsConfig)
so protocol-mapped custom provider groups resolve like the rest of the
CLI model surface, and document the remaining protocol-agnostic desktop
scan in the design doc. Correct the getHomeEnvFallback comment: it
adopts the narrower getHomeEnvFallbackVars candidate set on purpose.

Add multi-record DNS answer tests on both CLI and desktop net guards so
the records.some classification is pinned against the array shape
defaultLookupHost always produces in production.

* fix(voice): address round-9 review findings for trusted private base URLs (#8350)

* fix(voice): address round-10 review findings for trusted private base URLs (#8350)

* fix(voice): classify desktop voice duplicates before ambiguity check (#8350)

* fix(scripts): compare voice guard mirrors as parse trees (#8350)

---------

Co-authored-by: rockybot2026 <265985139+rockybot2026@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-08-06 14:04:57 +00:00
Shaojin Wen
35108c0cf0
ci(autofix): run heavy autofix jobs on the ECS pool with hosted fallback (#8603)
* ci(autofix): run heavy autofix jobs on the ECS pool with hosted fallback

The heavy autofix jobs (issue-autofix, build-cli, review-address) were
pinned to GitHub-hosted runners, where they hold up to five 300-minute
slots per scan. Combined with CI, that saturates the org's ~20 hosted
concurrency slots and starves the PR-review workflow's hosted-only jobs
(delay timer, fork authorize, /resolve).

Route the three heavy jobs to the persistent ecs-qwen pool using the same
repository-guarded expression as the rest of the fleet, with an
ubuntu-latest fallback for forks of this repo and for
MAINTAINER_ECS_RUNNER_DISABLED.

This is safe for the fleet's two secret-bearing agent jobs:
- Every review-address target is live-gated to write+ (internal) authors
  at scan AND address time; in-repo PR heads already require push access.
- CI_DEV_BOT_PAT is a write-level branch-push token with no privilege
  escalation (cannot bypass branch protection).
- The ECS pool ships docker, and repo-hygiene already runs the sandbox
  image there in production; sandbox containers use --rm with no fixed
  name, so concurrent legs on one machine do not collide.

Relax the 'Check runner environment' gate to accept self-hosted, and drop
the github-hosted condition on 'Set up Node.js' so Node 22 is provisioned
on ECS. The contract test is updated to pin the new ECS routing expression
(including the repository guard and hosted fallback) instead of the old
hosted-only one.

* ci(autofix): harden ECS-routed heavy jobs for the shared persistent pool

- Restore workspace ownership before checkout in issue-autofix,
  build-cli, and review-address, matching the sibling pool workflows
  (root-owned leftovers from containerised jobs break checkout/npm ci).
- Sanitize the reused workspace's local git config (allowlisted
  unset-all + symlink-aware hook sweep, qwen-triage's pattern) before
  the PAT-bearing git steps in issue-autofix and review-address.
- Give issue-autofix a per-run WORKDIR (/tmp/autofix-<run_id>): pool
  registrations share one /tmp and issue-phase runs never serialize.
- Drop stale local autofix/issue-* branches after checkout so a retry
  on the same registration cannot hit "branch already exists" or push
  a failed attempt's stale commits.
- Extend the ECS routing guard for PR-family events (same-repo head or
  write+ author), mirroring ci.yml's classify_pr convention, and fix
  the runs-on comments that claimed fork PRs already fell back.
- Stop persisting CI_DEV_BOT_PAT into the origin remote URL: publish
  pushes authenticate transiently, and the redundant set-url in
  'Push and report' is removed (push/fetch already use a URL inline).
- Pin all of the above in the workflow contract tests, reusing hoisted
  job-block constants.

* ci(autofix): harden ECS pool hygiene with shared hardened sanitize action

Review round for the ECS routing of the heavy autofix jobs. The
sanitize step shipped in the previous commit was the trusted-lane
variant of the qwen-triage sweep, which two verified bypasses defeat
(worktree-scoped config carrying core.hooksPath, and a global
core.hooksPath steering the hook deletion). Port the hardened variant
into a composite action so all three heavy jobs share one copy, and
add it to build-cli, whose checkout runs in the same reused workspace
and feeds the bundle every PAT-bearing leg executes.

Also completes the persistent-pool hygiene the routing depends on:
per-run/per-target WORKDIRs now chmod 700 at creation and are removed
by an always() teardown step after the artifact upload (nothing else
ever deleted them); the stale-branch sweep detaches HEAD first and
globs via BRANCH_PREFIX; the artifact upload reads env.WORKDIR
instead of a duplicated literal.

The tautological runner-environment gate is repurposed as a
fail-fast docker preflight (a missing daemon otherwise surfaces only
at 'Resolve sandbox image', after npm ci/build), and Install tmux
switches to sudo -n so hosts without passwordless sudo fail with a
clear message instead of dying on a prompt.

The runs-on comments are rewritten to say exactly what they guard:
which events carry the same-repo/write+ clause and which rely on the
live write+ or label gates, why review-address is an author-permission
gate rather than a head-repository gate, and where the pool's docker
availability is proven in-repo (qwen-triage's container jobs on the
same labels).

Contract tests pin all of it: hygiene steps present and ordered
before checkout in every heavy job, the hardened sweep's ordering and
containment in the shared action, the docker preflight and tmux
hardening, the WORKDIR lifecycle, hosted-only short jobs, and generic
next-job slice boundaries so a job inserted after review-address can
no longer hide inside its test slice.

* ci(autofix): inline git-config sanitize step before checkout

The composite sanitize action was invoked as a local action
(`uses: './.github/actions/sanitize-workspace-git-config'`) BEFORE
`actions/checkout` in all three ECS-routed jobs. A local action resolves
from $GITHUB_WORKSPACE, so:

- on any clean workspace it hard-fails with "Can't find 'action.yml'" —
  every hosted-fallback run (forks, MAINTAINER_ECS_RUNNER_DISABLED=true,
  i.e. the documented rollback path) and any freshly registered ECS
  runner — and it cannot self-heal, dying before checkout.
- on a reused workspace it executes the leftover copy, which for
  review-address is the previous run's PR branch — pre-checkout execution
  of PR-controlled content, the exact vector the step exists to prevent.

Inline the hardened sanitize script as a plain `run:` step in each job
(the qwen-triage 'Clean stale agent state' pattern) and delete the
now-consumer-less composite action. Update the contract test, which had
pinned the broken ordering, to pin the inline run step instead, and add a
generic guard that no local action may precede a checkout.

* ci(autofix): harden PAT handling and pool isolation on the shared pool

Address the Medium findings from the ECS-routing review:

- PAT pushes (Publish PR, Push and report, and the fork dry-run
  preflight) now authenticate via a transient credential helper instead of
  a token-bearing remote URL. The token enters neither the reused
  workspace's .git/config nor the process argv, which is world-readable
  via /proc on this shared host; the helper config holds only a
  ${GITHUB_TOKEN} reference and is swept by the next sanitize pass.
- 'Check runner environment' now asserts the self-hosted runner is an
  ecs-qwen pool member by name, so a mis-labelled registration cannot
  silently claim a PAT-bearing 300-minute job.
- 'Reset autofix workspace' age-sweeps abandoned /tmp/autofix-* dirs (a
  hard runner kill skips the always() teardown and run_id never repeats)
  and prunes the reused workspace's .git.

The contract test is updated to pin the tokenless push/fetch URLs, the
credential-helper authentication, and the absence of a token-bearing URL.

* ci(autofix): create autofix workdirs with umask 077 and correct cleanup comments

* ci(autofix): scope PAT credential helper to a one-shot git -c, plus review fixes

Address the latest review round on the ECS-routed heavy jobs.

High — the transient credential helper was not transient: `git config
--local credential.helper` wrote into the reused workspace's .git/config
and the matching unset was skipped on every error path (bash -e abort,
the fork-preflight exit 0, the salvage-loop exit 1s), stranding a helper
that answers any host with the PAT where ci.yml / pr-review jobs could
read it. All three push paths now use a one-shot, host-scoped
`git -c credential."https://github.com".helper=...` (a git_auth wrapper
for the salvage loop), so nothing is ever written to .git/config.

Medium — narrow the sanitize allowlist's `remote.*` to
`remote.<n>.(url|fetch|pushurl)` (uploadpack/receivepack are command
strings the sweep previously kept), and cover all three inlined sanitize
copies in the hardening test with a byte-identical pin (previously one
of three), rewording the stale "composite action" references.

Low — drop the recursive `rm -rf` hooks fallback that could wipe the git
dir itself (warn instead), extend the /tmp sweep glob to the legacy fixed
dir (`autofix*`), and make `git gc` threshold-driven (`--auto --prune=now`).

The contract test pins the git -c / git_auth form, the absence of any
config-written credential helper, and the tokenless URLs throughout.

* ci(autofix): pin gc to the foreground and anchor push auth assertions

Round-2 review follow-ups:

- `git gc --auto --prune=now` detaches by default (gc.autoDetach=true),
  and --prune=now is unsafe when other processes write concurrently —
  exactly what follows in these 300-minute jobs. Force it synchronous
  with `-c gc.autoDetach=false` so the repack still only fires when the
  --auto thresholds say so, but never races the checkout, agent commits,
  or merge/retry loop that come after it.
- Tighten the two push assertions (dry-run preflight, Publish PR) to
  require the host-scoped `git -c credential."https://github.com".helper`
  prefix immediately before the push, instead of a bare
  `push --no-verify …` match that a `git push` regression would still
  satisfy.

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-08-06 07:19:32 +00:00
Shaojin Wen
2c514b50b9
fix(autofix): serialize scan-and-pick issue runs in one concurrency group (#8435)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
* fix(autofix): serialize scan-and-pick issue runs in one concurrency group

The issue-phase concurrency group fell back to github.run_id for every
run that route did not force a target — which is every scheduled run and
every label-triggered run. Run-unique groups exclude nothing, so two
overlapping scans (cron effectively fires every 40-70 minutes while the
job may run for up to 180) could both pass the live label recheck during
the minutes-long assess call, double-claim the same issue, and burn two
multi-hour agent runs on it; the loser then fails its push and posts a
withdraw comment.

Key forced dispatches per issue, label events on the payload issue, and
all scan-and-pick runs (cron or unforced dispatch) on one shared
'scheduled' group. cancel-in-progress stays false so a superseded tick
still runs when targets remain.

* fix(autofix): make concurrency group pin prettier-stable (#8435)

* fix(tests): sync qwen-resolve-workflow timeout pins with repository variables (#8435)

* fix(ci): pin issue-phase concurrency group equal to FORCED_ISSUE (#8435)

* test(ci): anchor right edge of issue-phase concurrency group pin (#8435)

* fix(autofix): keep never-runnable runs out of the issue-phase concurrency group (#8435)

* test(autofix): pin issue-autofix concurrency gate to the job if predicate (#8435)

* fix(autofix): exclude dry runs from the issue-phase concurrency groups (#8435)

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

* test(autofix): pin the Claim/Publish dry-run step gates (#8435)

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

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

---------

Co-authored-by: verify <verify@local>
Co-authored-by: 易良 <1204183885@qq.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-06 05:27:39 +00:00
顾盼
8fd0162c68
fix(release): normalize Live Host signing identity (#8579) 2026-08-06 02:29:38 +00:00
Shaojin Wen
89b3d5ea8e
fix(autofix): ship core dist in the review CLI bundle (#8612)
* fix(autofix): ship core dist in the review CLI bundle

The review fan-out (#8548) dropped the per-leg build: legs restore the
shared bundle's repo-root dist/ and rely on the verify gate's full
build for branch verification. But the gate's settings-schema and i18n
checks run BEFORE any build, on every path including no-action, and
their tsx-transpiled cli sources import '@qwen-code/qwen-code-core',
which resolves through the workspace symlink to
packages/core/dist/index.js. With no build on the leg, the generator
crashes with ERR_MODULE_NOT_FOUND and the gate misreports a
deterministic "settings schema is stale" rejection (run 31031063525 on
PR 8600), then burns an 18-minute repair agent round on an environment
problem no agent can fix.

Ship packages/core/dist (+~8.5MB gzipped) alongside the root dist/ in
the fan-out artifact and assert its entry point on restore. This
restores exactly the pre-fan-out state: legs used to build the trusted
base themselves before the branch checkout, so the gate always ran
against base-built core dist. The workflow contract tests pin the new
tar command and the restore-side assertion.

* fix(autofix): rebuild branch-touched core dist before the schema gate

Review feedback on the core-dist bundle fix:

- Rebuild packages/core from branch sources in the review verify gate
  when the branch diff touches core's sources, so the pre-build
  settings-schema check never compares the branch's committed schema
  against a base-built dist (changed runtime constants) or crashes the
  generator (changed exports). Lives in the shared gate script so both
  the initial and the repair gate are covered.
- Narrow the bundle/restore comments and their test mirror to the
  settings-schema generator: the i18n check resolves core to sources
  via the packages/cli tsconfig paths map and needs no dist (verified
  empirically).
- Anchor the tar contract pin at end-of-line so additive path drift
  fails the suite instead of passing on a substring match.
2026-08-06 01:22:02 +00:00
Shaojin Wen
a123d0030a
ci(review): prepare evidence-image tooling for GitHub-triggered reviews (#8454)
* ci(review): prepare evidence-image tooling for GitHub-triggered reviews

Reviews triggered on GitHub cannot attach images today for three
reasons; this wires the two that live in the workflow:

- Install tmux and freeze (pinned, checksum-verified) before the
  review runs. Both are optional by contract — the evidence ladder
  degrades honestly without them (png -> ans-only -> refused, recorded
  in the capture manifest) — so the step never fails the review; it
  only decides which rung the runner can reach. tmux mirrors the
  tolerant install qwen-autofix.yml already uses; freeze falls back to
  ~/.local/bin when passwordless sudo is absent.

- Pass QWEN_REVIEW_ASSETS_REPO from a repository variable to the
  review step. Publishing stays OPT-IN by design: with the variable
  unset the env is empty and publish-assets refuses (parseAssetsRepo
  trims and rejects empty), so nothing changes until a maintainer sets
  the variable. When set, evidence images land on commit-pinned
  pr-assets/<pr>-review branches — already covered by the visuals
  cleanup workflow — pushed with the same CI_BOT_PAT the step uses.

The third reason is release lag: the capture producer (capture-tui,
#8388) has to merge and ship in a release before rendering claims can
generate images on CI at all. This change is inert until then.

* fix(ci): capture-tools step review fixes — enforced tolerance, version pin, cached fallback

R1-1: the never-fails contract is now enforced twice — continue-on-error
at the YAML level (the belt) and set +e with a trailing exit 0 inside
(the suspenders); under the runner's default bash -e several statements
(mktemp, install, sudo install with an empty path) could previously
abort the step and fail the review the comment promised never to fail.

R1-6: probe the VERSION, not just the binary — on a persistent
self-hosted runner an installed freeze made any FREEZE_VERSION/SHA
bump a silent no-op; the pin now forces a refresh when the cached
binary does not match.

Cached-fallback fix: put ~/.local/bin on PATH (and GITHUB_PATH) before
the probe — a sudo-less runner otherwise re-downloads the tarball on
every review run forever.

R1-3: the step comment says capture-tui is UPCOMING (#8388, not in the
released CLI) and names qwen review drive as today's tmux consumer, so
the step cannot be mistaken for stale dead weight and deleted from
under the follow-up.

R1-4: the retention comment scopes the cleanup-workflow claim to the
same-repository designation; a fork or scratch destination manages its
own retention (docs updated to match, plus a note documenting the
repository VARIABLE a maintainer sets to enable publishing).

R1-5: the step's real bash now runs in the workflow behavioural
harness under bash -e with stubbed sudo/apt/curl/sha256sum/tar/uname:
worst-runner and checksum-reject scenarios exit 0 installing nothing,
the no-sudo happy path pins the ~/.local/bin + GITHUB_PATH pairing,
and the version-pin probe is pinned from both sides (wrong version
re-downloads, matching version skips). Real freeze/sudo on a
developer machine are shadowed so the tests are deterministic and can
never install to /usr/local/bin.

Nit: both sudo guards now check sudo -n true.

* fix(ci): capture-tools step review fixes — step-owned tool dir, anchored probe, honest failures

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

* fix(ci): capture-tools test harness — shadow tmux, don't blank its PATH dir

The harness dropped every host PATH directory that ships a tmux so the
step's apt branch would depend on the scenario, not on the machine
hosting the suite. On GitHub-hosted ubuntu runners tmux lives in
/usr/bin, so the filter blanked /usr/bin wholesale — bash, grep, mkdir,
and tar included — and execFileSync('bash') died of ENOENT: all seven
capture-tools tests failed in the Test (ubuntu-latest Node 22.x) job
while passing on tmux-less dev machines.

Replace the directory-level drop with an entry-level shadow: each
tmux-bearing directory is mirrored (symlinks) into a scratch dir minus
the tmux entry, in place, preserving PATH order and the empty-entry
stripping the old filter did. Hosts without tmux take the map through
unchanged, and Windows (no tmux in its PATH, no symlink branch) keeps
its current behavior exactly.

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

* fix(ci): capture-tools test review fixes — faithful stubs, env shape pins, missing-branch scenarios

Round-3 review findings: the harness executed several of the step's
branches but asserted nothing about them, so probe-verified mutants
(dropped tmux guard, deleted warning/degradation messages, malformed or
missing FREEZE_VERSION/FREEZE_SHA256, wrong hash variable, dropped URL
`v` prefix or curl `-L`, severed tarball paths, broken regex boundary,
leaked mktemp dir) all shipped green.

- Make the curl/sha256sum/tar stubs model their real contracts: exact
  pinned URL, pinned checksum over a file curl actually wrote, existing
  -xzf operand
- Pin FREEZE_VERSION/FREEZE_SHA256 shape in captureToolsSource
- Pin the full curl flag set and the three-site tarball path agreement
- Assert the stale-renderer warning (fires on degraded re-download,
  silent on the happy path) and the tmux-unavailable message
- Pin TMPDIR and assert the mktemp cleanup leaves it empty
- Add the two missing scenarios: tmux-present skips apt, cached version
  extending the pin with a leading digit re-downloads

Verified by 13 mutation probes: every named mutant now turns the suite
red (13/13 killed), baseline 34/34 green.

* fix(ci): capture-tools step review fixes — hash-verified cache, per-run PATH promotion

* fix(ci): capture-tools step review fixes — verified-bytes-only installs, step timeout

Review findings on the capture-tools step:

- Drop the PATH-trust branch: a freeze already on PATH was accepted on
  its own --version and executed to probe it — exactly the self-report
  the FREEZE_BIN_SHA256 comment declares attacker-controllable, from
  dirs writable between jobs on both runner classes. The checksummed
  download always runs now; the cache makes it free after the first
  run.
- Guard $tools_bin in the download branch: with mktemp failing, the
  unguarded install resolved to /freeze — harmless unprivileged, but a
  root-in-container self-hosted runner writes it and reports success
  with nothing on PATH.
- Copy-then-verify the cache: install into the fresh per-run dir FIRST,
  verify THOSE bytes, delete both copies on mismatch — the verified
  bytes are the bytes later steps execute, closing the check-then-copy
  race for free. This makes the separate pre-verify block redundant;
  it is deleted.
- Add timeout-minutes: 5 — continue-on-error bounds failure, not
  duration, and a stalled `sudo apt-get update` mirror had no other
  bound under the 300-minute job cap.
- Report block: say the resolved freeze is likely broken when its
  --version produces nothing, instead of echoing a blank line and
  calling it stale; the mismatch wording is direction-neutral now.

Tests: replace the PATH-trust scenario with a planted-PATH one (marker
outside the scenario dir proves the plant never executes), add the
mktemp-failure scenario (the install stub succeeds like root would, so
the unguarded mutant is caught) and the promoted-dir 0700 assertion;
re-anchor the two digit-boundary tests on the report's warning. 41/41
green; both fix mutants verified killed.

* fix(ci): capture-tools review fixes — stale-dir cleanup, pinned guards

Address round-5 review:
- R5-1 (Critical): the per-run qwen-review-tools.* dir under RUNNER_TEMP
  was never removed; RUNNER_TEMP survives across jobs on the shared pool,
  so every review run accumulated one dir + one Go binary, unbounded.
  'Clean stale agent state' now removes stale dirs before the install
  step creates the current run's dir, matching the qwen-triage.yml
  convention. The harness comment claiming the dirs were runner-cleaned
  is corrected.
- R5-6: the cache re-verification rejection branch now logs why it
  deletes the cached binary instead of degrading silently.
- R5-7: bump-checklist note beside the freeze pins — the harness stubs
  key on the same env values, so a transposed hash pair must be caught
  against the real release artifacts at bump time.
- R5-2/R5-3/R5-4/R5-5: four unpinned step properties now pinned (the
  if: guard, the sudo -n probe flag, install-after-context ordering, and
  the cache branch's tools_bin guard via a new mktemp-fails scenario);
  six mutation probes confirm each pin kills its mutant.

* fix(ci): capture-tools review fixes — curl budget, swept scratch dir, wiring pins

* fix(ci): capture-tools review fixes — harness mutation pins, pin-pair self-check

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

* fix(ci): capture-tools review fixes — shadow-farm cleanup, backoff budget term

* fix(ci): capture-tools review fixes — report probes only installed freeze, age-gated sweep

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-08-05 13:16:56 +00:00
易良
c73b5ed887
ci: run Windows merge queue tests on ECS (#8386)
* ci: run Windows merge queue tests on ECS

* test(channels): skip POSIX mode assertion on Windows

* ci: expose Git Bash on Windows ECS runner

* ci: scope Windows ECS tuning to self-hosted and restore full test:ci

Review feedback on the Windows ECS routing: dropping test:scripts removed the only Windows execution of 9 Windows-only install-script tests, and the job-wide PowerShell default plus narrowed test command changed the kill-switch fallback away from the known-good hosted configuration.

Restore the full npm run test:ci on both paths (bash is available: pre-installed on hosted runners, exposed via the Git Bash PATH entry on ECS) and gate every ECS-specific adjustment on runner.environment: the PowerShell setup step (now also skip_ci-guarded), TEMP/TMP/LC_ALL env writes, and the Linux-style Node setup split that fails with an actionable error naming MAINTAINER_ECS_RUNNER_DISABLED. The windows-2022 fallback is byte-for-byte the pre-ECS job again.

* test: make Windows CI suites platform-aware

* ci: add stale-checkout guard to Windows ECS test job

* test(core): compare canonical directory identity

* ci: add fork guard and review follow-ups to Windows ECS job

* test(core): exercise real directory identity change

* test(core): wait for killed lease process exit

* test(scripts): avoid cmd echo trailing spaces

* test(scripts): use unambiguous cmd echo syntax

* test(cli): avoid sidecar I/O in truncation test

* test: fix Windows script-suite gaps and unify platform gating

- Fix missed trailing-space cmd stub in package-scripts.test.js so the
  'runs prepare steps in order' assertion passes on Windows.
- Add qwen-pr-review-workflow.test.js and pr-self-report-label.test.js to
  the win32 exclude list (both test Linux-only workflows and are not
  portable to Windows).
- Replace local itPosix/describeOnNonWindows consts with vitest's built-in
  it.skipIf/it.runIf/describe.skipIf, matching the codebase idiom.

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

* test(scripts): restore Windows workflow coverage

* test(scripts): re-exclude Windows-incompatible workflow tests on win32

Re-add pr-self-report-label.test.js and qwen-pr-review-workflow.test.js to
the win32 exclude list. Both fail on a Windows runner for reasons the code
still carries: qwen-pr-review-workflow.test.js calls execFileSync('mkdir'),
which has no executable to resolve there, and pr-self-report-label.test.js
joins PATH with ':', corrupting the ';'-separated Windows PATH so its gh
stub never resolves. Excluding them restores a green Windows gate; Linux CI
remains their authoritative coverage. Document the criterion inline.

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

* ci: extract checkout-head guard into composite action, pin Windows gate (#8386)

Address review round 2: move the stale-checkout guard shared by the four CI gates into .github/actions/verify-checkout-head so the copies cannot drift, pin the Windows gate kill-switch routing and guard wiring in the script tests, re-enable lint.test.js on Windows via separator normalization and a lazy linter setup in scripts/lint.js, unify the platform skips on it.skipIf(process.platform === 'win32'), and document the queued-run behavior of the ECS kill switch.

* ci: fail fast in Windows gate environment setup (#8386)

* ci: dedupe self-hosted runner steps into actions, pin gate mutations (#8386)

* fix(ci): checkout before repository-local actions in Windows gates (#8386)

* fix(ci): configure Windows runner before bash guard

* test(ci): pin remaining shared-action wiring in script tests (#8386)

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

* fix(ci): skip zip-dependent packaging tests when zip is missing (#8386)

* fix(ci): validate full Windows smoke path

* fix(ci): match Windows smoke shell to gate and drop dead runs-on guard (#8386)

* fix(ci): make SIGTERM escalation test Windows-aware and tighten pins (#8386)

The CDP acceptance test asserted a POSIX-only SIGKILL escalation, which
fails deterministically on Windows where kill('SIGTERM') terminates the
child directly — blocking the Windows merge-queue gate. Assert the
platform-appropriate signal instead.

Also address review suggestions: probe `unzip` alongside `zip`, pin the
integration_cli guard's missing step-level `if:`, stop getWorkflowStep
at unnamed steps, pin install-script.test.js out of the win32 excludes,
add the stale-checkout guard to windows-runner-smoke.yml, pin the
Node preflight warning branch and the guard reject path contiguously,
and extend the smoke shell-parity loop to the npm cache step.

* docs(ci): clarify Windows runner trust boundary

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci@service.alibaba.com>
2026-08-05 12:14:42 +00:00
顾盼
93cd019088
fix(release): reuse configured Apple signing secrets (#8574) 2026-08-05 09:20:17 +00:00
顾盼
a5c637b749
feat(web-shell): add native Live Voice (#7859)
* feat(web-shell): add native Live Voice

* fix(web-shell): address review feedback for Live Voice PR (#7859)

- Quote all strings in electron-builder.yml to fix yamllint CI failure
- Gate discovery publish on liveVoiceEnabledAtBoot to avoid writing
  bearer token to disk when Live Voice is disabled (M1)
- Add child identity guard to CommandMonitor stdout/stderr handlers
  to prevent stale helper output from corrupting the new buffer (M4)
- Add exponential backoff to sent-completion delivery retry (M3)
- Skip broadcastState when setCallState/setTranscript value is
  unchanged to reduce per-audio-delta overhead (H1)
- Document sent-mode completion notification in module docstring (H2)
- Remove dead protocol/nonce aliases from readDiscoveryFile
- Fix single instance lock fall-through with process.exit(0)

* fix(cli): register realtime_voice in docs contract and env guard (#7859)

* fix(web-shell): address review feedback for Live Voice PR (#7859)

* fix(cli): discard orphaned isolated dir when parent restore fails (#7859)

* fix(web-shell): address review feedback for Live Voice PR (#7859)

* fix(serve): harden live turn recovery

* fix(desktop): restore Live Host native build

* fix(live): align native host and session isolation

* fix(acp): preserve live worker continuation lineage

* fix(live): classify provider close reasons

* fix(serve): discard unused recovered conversation dirs

* fix(live): isolate authorized realtime responses

* fix(live): preserve realtime response authority

* feat(web-shell): complete Live Voice onboarding

* fix(live): persist realtime-owned dialogue

* fix(live): preserve final speech while stopping

* Revert "fix(web-shell): address review feedback for Live Voice PR (#7859)"

This reverts commit 7110bec6b034c702bca6e28e35b93c7f70e729cd.

* Revert "fix(cli): discard orphaned isolated dir when parent restore fails (#7859)"

This reverts commit 85165f1b2ddfaa311b8be91acdd76a6f388f6204.

* Revert "fix(web-shell): address review feedback for Live Voice PR (#7859)"

This reverts commit 9199fa633e102bb8f24e4b216d322be4323eb3fc.

* Revert "fix(cli): register realtime_voice in docs contract and env guard (#7859)"

This reverts commit 6b6b1718352ef01a98a73976b5c7c4433fd14c35.

* Revert "fix(web-shell): address review feedback for Live Voice PR (#7859)"

This reverts commit e083779105199d26de3afd8ad00719a08efe3099.

* revert(live): remove remaining takeover behavior

* revert(live): restore pre-rollback implementation

* test(cli): align Live diagnostics env guard

* test(release): cover Live Host publication

* fix(ci): re-sign Live Host package before verification

* fix(serve): scope sent completion notifications to Live

* fix(web-shell): preserve live setup errors

* fix(live): align realtime backend speech lifecycle

* ci(live): publish Live Host independently

* test(cli): mock Live speech bridge handler

* test(release): align Live Host workflow contract

* fix(live): address release and lifecycle review findings

* fix(live): release completed call tracking

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
2026-08-05 08:33:22 +00:00
Shaojin Wen
da37110e60
perf(autofix): build the review CLI bundle once per scan and fan it out to legs (#8548)
* perf(autofix): build the review CLI bundle once per scan and fan it out to legs

Each review-address leg repeated the same trusted-base build: measured
3.5-5 minutes of npm ci + build + bundle per leg (~25 runner-minutes on
one 6-leg scan) before the agent could start. A build-cli job now
compiles the bundle once per scan, uploads the repo-root dist/ as an
artifact, and the legs download it; their checkout is pinned to the
compiled SHA so a mid-run base push can never pair a leg's bundle with
different sources. The legs keep npm ci (the agent and the verify gate
still need node_modules against the PR branch), and the issue phase is
untouched — it runs only when no review targets exist, so gating the
build on do_issue too would rebuild on every quiet scheduled tick.

* fix(autofix): validate fan-out bundle SHA and pin shared CLI recipe contracts (#8548)

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

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-05 05:16:49 +00:00
易良
e34780e24d
fix(ci): clean review worktrees after cancellation (#8474)
* fix(ci): clean review worktrees after cancellation

* fix(ci): remove orphaned review worktree directories

* fix(tests): sync qwen-resolve-workflow expectations with externalized review timeouts (#8474)

* fix(ci): pin review worktree cleanup patterns to paths.ts (#8474)

* fix(ci): harden review cleanup sweeps and cover integration_cli (#8474)

* fix(ci): extend review cleanup sweep to web_shell_e2e_smoke (#8474)

* fix(ci): harden review cleanup git calls

* fix(ci): tighten review cleanup comments and test guards (#8474)

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

* test(ci): pin review cleanup recipe copies byte-identical (#8474)

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

* fix(ci): guard review worktree removal and pin cleanup invariants (#8474)

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

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-05 02:39:36 +00:00
Shaojin Wen
2601d815dd
test(ci): align resolve-workflow timeout expectations with externalized variables (#8460 follow-up) (#8485)
* test(ci): align resolve-workflow timeout expectations with externalized variables

#8460 moved the review timeout numbers into repository variables
(QWEN_REVIEW_JOB_TIMEOUT_MINUTES / QWEN_REVIEW_MAX_TIMEOUT_MINUTES)
but left qwen-resolve-workflow.test.js expecting the old hardcoded
300/240 values — main's Test lane has been red since. The five
expectations now pin the variable-driven forms the workflow actually
carries.

* test(ci): pin resolve-workflow timeout enforcement, tiering, and fallback machinery

* test(ci): pin the timeout-advice branch CONDITION, not just both bodies

With both branch bodies asserted as substrings of the same step, any
comparison flip (-ge/-gt/-le) keeps both strings present and ships the
wrong recovery advice on every timeout — a below-max run told it
already used the maximum, an at-cap run told to retry with a timeout
that cannot work. Raised in #8388's review round (R4-21).

* test(ci): anchor resolve-workflow arm slices at their if starts

Address review feedback on #8485: search for each sliced arm's else from
the arm's own if start so an unrelated earlier if/else in the step cannot
invert the slice (or vacuously satisfy .not.toContain on an empty slice).
Also slice the small-PR tier arm instead of comparing first-occurrence
indexes, unifying the technique and proving the assignments sit in
opposite arms.

* test(ci): keep the max-timeout text out of the below-max arm slice

* test(ci): pin fallback arm bodies, comment wiring, and tiering order

* review: line-anchor the at-max arm's closing fi

A bare indexOf('fi') stops at the first word CONTAINING the letters —
'specified', 'notification' — so a harmless wording change inside the
at-max arm would silently truncate the slice and hand the arm's
not-toContain a vacuous pass. The end anchor is now the line-anchored
/\n\s*fi\b/, with its own found-assertion.

---------

Co-authored-by: Qwen Autofix <autofix@qwen-code.dev>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: verify <verify@local>
2026-08-05 02:33:04 +00:00
易良
3ad4fbdb7d
fix(cli): preserve Qwen Review startup version in footers (#8431)
* fix(cli): preserve review startup version in footers

* fix(cli): keep review startup version dynamic in bundle

* fix(cli): reset review version after managed update

* test(cli): use indexed env access

* fix(cli): harden review footer strip and version stamping (#8431)

* fix(tests): sync qwen-resolve-workflow expectations with externalized review timeouts (#8431)

The timeout externalization in #8460 replaced the hardcoded 300/240
values in qwen-code-pr-review.yml with the QWEN_REVIEW_JOB_TIMEOUT_MINUTES
and QWEN_REVIEW_MAX_TIMEOUT_MINUTES repository variables but left
scripts/tests/qwen-resolve-workflow.test.js asserting the old literals,
so the full-profile Test job fails on any branch carrying that change.
Update the three affected assertions to pin the externalized shape.

* fix(cli): harden the review footer strip per review feedback (#8431)

The strip regex kept a 2^(N-1) partition ambiguity for same-line footer
runs (measured 5.3 s at n=20) and missed footers truncated before their
closing `_`; forged footers also survived on the body channel through
`bodyCriticals`, and the values interpolated into the footer were not
shape-validated. Guard the repeated group so an iteration cannot span
another footer's start, make the final `_` optional, strip body
Criticals per entry, refuse footer-forging model ids and non-version
stamps, refuse non-object comment entries, pin the CLI-glue test suite
against an ambient startup stamp, and cross-assert the LGTM filter
regex against the footer builder.

* fix(cli): strip review footers before ledger carryover

* fix(cli): align ledger footer regression expectation

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci@service.alibaba.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-04 14:58:56 +00:00
Shaojin Wen
d2c7cb1f15
fix(autofix): normalize paginated fetches to one flat array per file (#8438)
* fix(autofix): normalize paginated fetches to one flat array per file

gh api --paginate emits one JSON array PER PAGE, so any PR past 100
comments/reviews/events produces a multi-document stream. The workflow
already slurps correctly in a few readers (jq -rs add, --slurpfile +
add), but more than a dozen plain-jq consumers of the WORKDIR files
mis-aggregate on a multi-doc input:

- MARKERS/REARM_AT/RED_HEAD/REARM_KEY in the scan and their LIVE_*
  mirrors in prepare emit one result per page; ROUND then becomes a
  multi-line string, [[ -ge ]] arithmetic fails, and the round cap
  silently stops holding — on exactly the PRs (takeover, 100-round cap,
  one report comment per round) that reach page two first.
- CAP_NOTICED / BASE_UPDATE_RECENT / LAST_REJECTION / PRIOR_TIMEOUTS /
  the milestone census and the report-step consecutive-failure census
  all degrade the same way.
- NEWEST and LIVE_NEW bind rv/rc/ic/checks POSITIONALLY (.[0]..[3]); a
  two-page rv.json shifts rc/ic into the wrong slots and later
  feedback is silently lost.

Fix at the fetch sites: every --paginate that lands in a WORKDIR json
file (and the report step's COMMENTS_JSON fallback) now pipes through
jq -s 'add // []', so each file holds ONE flat array. Existing
slurp-style readers are unaffected — add is idempotent over a single
array — and every plain consumer becomes correct past 100 items with no
program changes. Failure semantics are preserved: the workflow-level
bash default gives -eo pipefail, so a failed gh still fails the
pipeline exactly where it failed the bare redirect before, and the
pr-events/COMMENTS_JSON fallbacks keep their '[]' paths.

The check-runs/annotations/status-comment reads stay raw on purpose:
they aggregate per-page via --jq + slurp, line-streams, or .[][] and
were already pagination-safe.

Tests: a behavioral case runs the real MARKERS→ROUND pipeline and the
positional NEWEST program against two-page fixtures through the
normalizer, with negative controls demonstrating the pre-fix corruption
(two MARKERS lines; the page-2 review timestamp lost to slot shift).
Shape assertions pin all nine normalized fetch sites and ban raw
--paginate file redirects.

* fix(autofix): pin total --paginate occurrence count in tripwire test (#8438)

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

* fix(autofix): correct gh --paginate merge model in pagination comments (#8438)

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

* fix(autofix): make engage-ack ic re-fetch atomic on failure (#8438)

* test(autofix): pin atomic engage-ack re-fetch and empty-input normalization (#8438)

---------

Co-authored-by: verify <verify@local>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-04 14:14:52 +00:00
Shaojin Wen
10291e16f2
refactor(core): move review skill incident narratives to DESIGN.md (#8499)
* perf(review): issue independent setup calls in one response

Measured on a real small-PR run: the stretch from parse-args to the
first agent launch took 7 minutes of wall clock, one round-trip at a
time, on calls that never needed an order — pr-context, comment-status
and the Step 2 rules load are mutually independent reads. Step 1 now
tells the orchestrator to issue all three in a single response (the
same rule Step 3 already enforces for the agent fan-out) and to page
their outputs in shared responses too. comment-status loses its
wait-for-the-context-file guard in worktree mode: learning whether
inline comments exist cost a serial round-trip, while running it on a
commentless PR just writes an empty index. Step 6's two deterministic
gates (script-lint, test-plan) get the same one-response note.

The orderings that matter are kept explicit: fetch-pr before everything
(it creates the worktree and the plan), the roster after the rules load
(it bakes the rules into every brief).

* refactor(core): move review skill incident narratives to DESIGN.md

SKILL.md is injected wholesale into the review orchestrator's context on
every /review run and re-billed on each of its turns, and ~16KB of it was
incident narrative — accounts of past dogfood failures and measurements
that justify rules but are not themselves instructions.

Move 50 such narrative blocks into a new 'Measured incidents (moved from
SKILL.md)' section of DESIGN.md (47 anchors, not loaded at runtime),
leaving every rule in place with a short
'(measured; DESIGN.md — <anchor>)' pointer. Force-bearing figures stay
inline where the number is the argument (e.g. the ~161s cold npm ci, the
41% test-code median, the PR #6457 one-of-five checklist measurement).

No instruction, gate, format, flag, threshold, or ordering changed; the
YAML frontmatter and all 35 fenced code blocks are byte-identical, and
the MUST / Do not / never imperative counts are unchanged outside the
moved narrative text (verified by script).

SKILL.md: 237,847 -> 228,266 bytes; DESIGN.md: 106,708 -> 125,184 bytes.

* fix(review): keep DESIGN.md out of the runtime bundle and pin pointers

The slim refactor left DESIGN.md shipped beside SKILL.md in
dist/bundled/, so one curious read_file of the 125 KB maintainer
document would cost more context than the refactor saves. The bundle
copy now skips DESIGN.md, and SKILL.md gains a one-line guard telling
the orchestrator the pointers are for humans auditing a rule.

Also addresses review feedback: a test pins both directions of the
SKILL.md incident-pointer mapping, the transcribed-argument narrative
keeps its referent after the move, the incidents section title loses
its changelog suffix, and Step 2 no longer asks for a base fetch that
fetch-pr already performed.

* fix(review): gate setup batching by effort and consolidate incident blocks

Address round-1 review feedback on the skill-slim PR:

- Gate the ONE-response setup batch and the comment-status call to high
  and medium effort, matching Step 2's low-effort skip.
- Scope the Step 6 lint/test-plan batching to same-repo PR reviews.
- Merge same-run incident blocks (self-composed Approve into the
  paraphrased roster prompt; archive verdict into the narrated-away cap),
  cross-reference the roster-size and relocated-Critical tellings, and
  state the #8368 path in its block plus the pointer it was missing.
- Pointer-ize the last inline QQChannel narrative and fix the
  scripts-nobody-ran summary to match its block.
- Extend the DESIGN.md exclusion to copy_files.js so the transpiled
  dist/src build and the published core tarball stop shipping it.
- Pin the no-read_file guard and the batch ordering constraints in
  SKILL.test.ts, and fail loudly on pointers the regex cannot parse.

---------

Co-authored-by: verify <verify@local>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
2026-08-04 12:41:18 +00:00
Shaojin Wen
06cc41ee3f
ci: route trusted-author fork PRs and no-checkout jobs to the ECS pool (#8502)
Some checks failed
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
npm cache producer / Save npm cache (push) Has been cancelled
* ci: route trusted-author fork PRs and no-checkout jobs to the ECS pool

Fork PRs whose author has write access (OWNER/MEMBER/COLLABORATOR association) now run Linux CI on the self-hosted ECS pool instead of the saturated GitHub-hosted quota, and bot workflows that check out no code move to ECS unconditionally. Everything stays gated on the MAINTAINER_ECS_RUNNER_DISABLED kill-switch.

* ci: address review — real write-permission routing, watchdog independence, timeouts

Route the triage agent on the collaborator-permission API result computed by authorize instead of the coarse author_association, which admits org members and read-only collaborators; the two permission-gate jobs revert to the same-repo guard. Keep the fleet watchdog and the CI-failure reporter hosted so they stay independent of the pool they watch. Add missing timeouts, wipe serve-ab's reused workspace, and pin the routing logic with drift and negative-case tests.

---------

Co-authored-by: 易良 <1204183885@qq.com>
2026-08-04 03:48:24 +00:00
Shaojin Wen
d6f55a1c95
fix(ci): align review workflow tests with externalized timeout variables (#8486)
#8460 moved the review timeouts into the QWEN_REVIEW_JOB_TIMEOUT_MINUTES and QWEN_REVIEW_MAX_TIMEOUT_MINUTES repository variables but left the workflow-text assertions in scripts/tests/qwen-resolve-workflow.test.js pinned to the old hardcoded 240/300 values, so the workspace test suite fails (Release Quality Checks and the PR Test job).
2026-08-03 23:01:49 +00:00
易良
fddcda86ed
ci: reduce SDK Java runner queueing (#8441)
* ci: cancel stale SDK Java pull request runs

* fix(ci): preserve SDK Java push scheduling

* ci: route trusted SDK Java jobs to ECS

* ci: simplify SDK Java runner routing

* fix(ci): provision Maven on ECS Java jobs

* fix(ci): clean SDK daemon ECS test state

* fix(ci): satisfy SDK Java lint
2026-08-03 16:21:05 +00:00
Shaojin Wen
72bd3dccc2
ci: remove broken legacy scheduled PR triage workflow (#8434)
The Gemini-era scheduled PR triage workflow has been dead weight for a
long time:

- Its only business value — syncing labels from the linked issue to the
  PR — never fires: gh exports closingIssuesReferences as a flat array,
  so the script's '.closingIssuesReferences.nodes[0].number' jq path
  always errors, the error is swallowed by 2>/dev/null, and every PR
  falls into the "No linked issue found" branch. The latest production
  run logged 157 "No linked issue" hits and zero label syncs, despite
  many of those PRs having linked issues.
- LABELS_TO_REMOVE is computed but never applied, PRS_NEEDING_COMMENT is
  never appended to, and the prs_needing_comment job output has no
  consumer — the rest of the script is dead code.
- It burns 1+N API calls against every open PR every 15 minutes.
- The id-token: write permission is a leftover from the Gemini/GCP OIDC
  era; nothing in the bash script uses it.

Real PR triage lives in qwen-triage.yml. Remove the workflow and its
script, drop the stale docs section describing behavior it never had,
and pin the file into the legacy-workflow regression list.

Co-authored-by: verify <verify@local>
2026-08-03 10:20:42 +00:00
Shaojin Wen
563f744329
feat(ci): size-aware default timeout for the PR review job (#8377)
The review-pr job ran every PR with a fixed 180-minute default budget.
A medium PR (#8241, +1577/-57) exhausted it and was killed at exactly
180 minutes without ever posting its review, while the same review can
finish in ~90 minutes on a less loaded runner.

When the caller does not pass an explicit --timeout, size the default
budget by the diff (additions + deletions): small PRs (<= 300 lines)
keep the proven 180 minutes, and anything larger gets the full 240
cap. An explicit --timeout=N still wins, and a failed size lookup
falls back to 180 rather than failing the review. Raise the job-level
timeout from 260 to 300 so the 240 budget plus the shared retry and
comment posting fit with headroom.
2026-08-02 11:28:16 +00:00
Zqc
07e35bf02f
feat(ci): add repo-hygiene skill and weekly patrol workflow (#7908)
* feat(ci): add repo-hygiene skill and weekly patrol workflow

Add .qwen/skills/repo-hygiene (SKILL.md + scripts/run-agent.mjs) and the corresponding GitHub Actions workflow that runs a weekly scan-and-fix patrol every Monday 03:00 UTC.

The skill defines a single scan-and-fix mode that scans the repo for small, certain docs/test/code hygiene issues (broken Markdown emphasis, test coverage truthfulness, contract mismatches, real boundary conditions), batches up to 8 fixes as individual Conventional Commits on ONE branch, and writes findings.json + bilingual report-only.md + PR body. High-risk paths (packages/core/src/**, auth/providers/models/config/tools/services) are report-only — they go to the PR comment, never the diff.

The workflow implements the trust split: agent never holds GitHub credentials; only the workflow publishes. Includes dedup (skip when a hygiene PR is already open), PAT identity verification, an independent verification gate (build/typecheck/lint + per-package vitest --changed), and a structural gate (commit cap, total diff cap, report-only path assertion).

Closes #7383.

* docs(repo-hygiene): expand judgment scans to six parallel search angles

Port the richer search-angle taxonomy from the operator's interactive loop-hygiene prompt into the SKILL.md.

Section B (judgment scans) now enumerates six explicit angles — test-coverage truthfulness, implementation/contract mismatch, resource lifecycle, real boundary conditions, user-visible configuration/API, and docs-as-secondary-scan — each with a grep/code-reference evidence requirement. The previous three-bullet version collapsed resource lifecycle and user-visible config/API into adjacent items, which meant the agent under-scanned AbortController/finally/iterator-return/stream-cleanup gaps and config-vs-schema disagreements in practice.

Also makes the subagent workflow explicit: subagents report candidates only and never touch the working tree; the main agent deduplicates and decides. This matches the operator prompt's 'retrieval subagent only reports, never modifies' constraint.

* refactor(repo-hygiene): drop deterministic-docs section, fold its principles into the scan loop

Section A (Deterministic docs patterns) was too narrow: it gave two specific rg commands that dated quickly and duplicated the docs-as-secondary-scan rule already present in the judgment-scan taxonomy. Remove the section and promote Section B to the only scan-targets list.

Two of Section A's cross-cutting principles are preserved by folding them into the scan loop's opening paragraph and the Docs bullet:
- 'a pattern hit is a lead, not a finding' — now applies to rg, grep, and any other scanner
- 'broken-but-rendering-fine emphasis stays untouched' — now part of the Docs bullet's exclusion list

* refactor(repo-hygiene): partition the scan into nine parallel subagents

The previous Scan Targets section dispatched subagents by search angle (six parallel scans of the whole monorepo). Each subagent ended up with a shallow read of every file it touched — it couldn't hold a module's contract graph in context, so findings tended to be one-line-deep (docs-vs-code, rendering bugs, dead-code hints).

Switch to partition-first dispatch: one subagent per of nine module boundaries (cli/config, cli/runtime, cli/ui, core, extensions, sdk-typescript, sdk-python-java, ui-apps, docs). Each subagent owns its partition, finds the package's own entry points/schemas/registries, then applies the six search angles inside that scope. The main agent deduplicates across partitions afterward.

A subagent must stay inside its partition; cross-partition findings are marked crossPartition:true for the main agent to merge or drop. Reading lists are intentionally not prescribed — the subagent picks the partition's own sources of truth.

* refactor(repo-hygiene): allow cross-partition tracing, drop anti-padding bullets

Two simplifications:

1. Replace 'subagent must stay inside its partition; crossPartition:true' with 'partition is a starting boundary, not a fence'. A subagent may now follow a call chain, import graph, or contract reference into another partition to build evidence. When a finding's evidence lands in a report-only path (core/*, auth/*, providers, models, config, tools, services, cross-package contracts), it goes to reportOnly — never into fixes. This replaces the previous crossPartition handoff machinery with one uniform rule that already exists in Scope Limits.

2. Drop two anti-padding bullets from Scope Limits ('do not batch trivial fixes to reach quota', 'do not manufacture findings to fill the run'). The Shared Rules' 'no speculative edits / keep changes minimal and scoped' already covers the same behavior; restating it here was noise.

* docs(repo-hygiene): fix stale 'scans A and B' reference in scan-and-fix mode

The A/B section split was collapsed into a single Scan Targets section two commits ago, but step 1 of Mode: scan-and-fix still said 'Run scans A and B'. Reword to describe the actual dispatch: nine partition subagents, six angles inside each, collect and deduplicate across partitions.

* refactor(repo-hygiene): switch report-only rule from path to impact scope

Drop the path-based report-only whitelist (packages/core/src/**, */src/auth/**, providers/models/config/tools/services/**, cross-package contracts). The report-only trigger is now impact scope: a finding whose minimal fix touches more than three files or more than one hundred lines of production code is report-only.

Rationale: the path list was a proxy for 'high-impact area' that over-blocked trivial, certain fixes inside those paths and under-blocked sprawling fixes outside them. The file/line threshold measures impact directly. Scope Limits already caps each fix at 20 lines of production diff, so anything past 100 lines is an order of magnitude beyond that — past the four-file mark is past the single-root-cause test.

* docs(repo-hygiene): expand each partition with package layout and correctness criteria

The previous Nine-partitions section was a one-line scope pointer per partition. Each partition is now a mini architecture block naming the package path, what the package does, its key subdirectories, and what 'correct' looks like inside the partition. Directory names verified against the current tree (cli/src/{commands,serve,acp-integration,services,remoteInput,dualOutput,startup,i18n,utils,core,export}, cli/src/ui/{agent-view,arena,extensions,mcp,hooks,subagents,background-view,shared,messages,contexts,themes,state,layouts,voice,selection,editors,daemon,models,noninteractive}, core/src/{agents,models,providers,tools,services,prompts,utils,hooks,memory,skills,subagents,permissions,confirmation-bus,mcp,lsp,ide,goals,resources,followup,extension,config,telemetry,output,qwen}, desktop/apps/{electron,viewer}, web-shell/client).

* refactor(ci): replace path-based gate with per-commit impact-scope check

The workflow gate previously rejected any branch touching core/src or
auth/providers/models/config/tools/services paths. This conflicted with
the SKILL.md's impact-scope rule (>3 files or >100 lines = report-only).

Replace with a per-commit check: each commit must touch ≤3 files and
have ≤100 diff lines. This aligns the deterministic gate with the
agent's report-only threshold.

* chore(ci): allow repo-hygiene workflow on fork for testing

* refactor(repo-hygiene): remove fix cap, file report-only as consolidated issue

- Remove MAX_FIXES (8) and MAX_TOTAL_DIFF_LINES (150) caps from workflow gate
- SKILL.md: no limit on fixes per run, only per-commit scope threshold
- Add workflow step to create single GitHub issue for all report-only findings
- Issue is deduplicated against existing open hygiene issues
- Per-commit impact-scope gate (≤3 files, ≤100 lines) remains unchanged

* fix(repo-hygiene): per-fix typecheck prompt + auto-revert on verification failure

- SKILL.md: require typecheck after each individual fix, not batched
- Workflow: typecheck runs first with auto-revert loop; bad commits are
  dropped while good ones survive, then full build+lint+test runs
- Prevents one bad fix from killing the entire patrol run

* fix(repo-hygiene): forbid retrying failed fixes to avoid loop detection

Agent got stuck retrying typecheck fixes repeatedly, hitting the
per-turn tool-call cap. Explicitly instruct to accept failures as
report-only and move on without retrying.

* fix(repo-hygiene): use only existing labels, structured failure handling

- Remove HYGIENE_LABEL env var; PR dedup by title+author search instead
- Issue uses 'bug' label (always exists); dedup by title search
- Remove label creation step
- SKILL.md: give agent concrete 3-step failure protocol (checkout,
  move to reportOnly, continue) instead of vague 'don't retry'

* fix(repo-hygiene): remove failure protocol, add anti-loop instruction

- Remove 3-step failure handling protocol (was over-engineered)
- Restore original 'write failure.md' behavior for failed verification
- Add: never re-issue identical tool calls (prevents loop detection)

* feat(repo-hygiene): add scan/fix split mode for two-phase execution

- SKILL.md: add Mode: scan-only and Mode: fix-only sections
- Workflow: add mode input (scan-and-fix/scan/fix) + scan_run_id input
- run-agent.mjs: add scan and fix specs with proper inputs/outputs
- Scan mode: only scans, uploads findings as artifact (~15min)
- Fix mode: downloads previous scan findings, only fixes (~25min)
- Gate/verify/push skipped for scan-only mode
- Branch name resolution skipped for scan-only mode

* refactor(repo-hygiene): split into scan + fix jobs in one workflow

Two-phase design within single workflow run:
- scan job: read-only, dispatches subagents, writes findings.json (~15min)
- fix job: depends on scan, downloads findings, fixes + verifies + pushes (~25min)

Benefits:
- Each phase stays within model's tool-call budget
- Scan failures don't waste fix-phase budget
- Fix failures don't require re-scanning
- phase input: both (default), scan, fix (with scan_run_id)

* simplify(repo-hygiene): remove phase/mode inputs, always scan then fix

Users just trigger the workflow. Scan and fix jobs run in sequence
automatically. No need for phase selection or scan_run_id.

* simplify(repo-hygiene): remove Mode sections from SKILL.md

- Replace three Mode sections with single 'Execution Steps' heading
- Step 1: scan phase (stop after findings.json)
- Step 2: fix phase (read findings, fix, verify, write PR)
- run-agent.mjs: invocation now tells agent which phase it's in
  using plain English instead of slash commands

* fix(repo-hygiene): use plain expression in job-level if condition

Remove ${{ }} wrapper from job-level if conditions to avoid
potential YAML parsing issues.

* fix(repo-hygiene): configure git remote with PAT before push

Checkout uses persist-credentials: false, so git push needs explicit
authentication via remote URL with x-access-token.

* fix(repo-hygiene): use single quotes for all if conditions

yamllint requires single-quoted strings. Changed all double-quoted
${{ }} expressions to single-quoted with escaped inner quotes.

* feat(repo-hygiene): gate drops oversized commits instead of failing

- Collect oversized commits during scan loop
- Capture commit messages before rebase
- Use git rebase -i to drop only the bad commits
- Move dropped findings from fixes to reportOnly in findings.json
- Continue with remaining commits instead of aborting the run

* refactor(repo-hygiene): split SKILL.md into per-phase documents

- SKILL.md keeps only shared content: rules, scope limits,
  findings.json format, output contract
- scan.md: scan targets (9 partitions, 6 angles) + scan steps
- fix.md: fix steps
- run-agent.mjs concatenates SKILL.md + phase doc by mode, so each
  phase's prompt contains only what that phase needs
- Drop the unused scan-and-fix mode

* refactor(repo-hygiene): move phase docs into references/ per skill convention

Match the triage/docs-audit skill layout: SKILL.md is the entry with
shared rules and a Workflow routing section; phase details live in
references/scan.md and references/fix.md. The CI runner still injects
the right phase doc into the prompt; interactive invocations follow
the routing section instead.

* refactor(repo-hygiene): adopt references/ progressive-disclosure layout

Match the triage skill convention: SKILL.md is the single entry with
shared rules and a Workflow routing section; phase details live in
references/scan.md and references/fix.md which the model reads itself.
The runner now sends only SKILL.md plus a one-line phase invocation.

* docs(repo-hygiene): drop redundant duplicate-tool-call rule

Loop guard already covers real loop scenarios; forbidding same-arg
calls at the SKILL layer added no value for a 9-subagent scan with
independent budgets and a state-heavy fix phase.

* fix(repo-hygiene): enable agent tool for scan and preserve partial findings

Scan settings never whitelisted the agent tool, so the nine-subagent
dispatch was impossible and the model scanned serially, blowing the
50-minute budget with zero output. Add agent to scan coreTools, teach
the serial fallback to write findings.json incrementally after each
partition, and upload agent.log/failure.md so failed scans can be
diagnosed.

* chore(repo-hygiene): drop fork-only repository guard

* chore: drop unrelated advisor design draft from branch

* fix(repo-hygiene): harden gate, dedup, and verification semantics

- Zero-fix runs are now green: fix phase stops silently instead of
  writing failure.md when the fixes array is empty
- PR dedup matches by hygiene/ branch prefix instead of trusting the
  agent-written title to contain 'repo-hygiene'
- Gate drops oversized commits in one rebase, removing the implicit
  newest-first ordering dependency of sequential rebases
- Dropped and reverted commits are appended as notes to pr-body.md so
  the PR description matches what is actually in the branch
- Scan job timeout raised to 75min (agent 50min cap + ~10min setup left
  a negative margin); typecheck output no longer suppressed

* fix(repo-hygiene): raise agent timeout for nine-partition scans

The v4 local run timed out at the 50-minute default while the v5 run
completed successfully but close to the limit. Nine parallel subagents
need more headroom, especially on slower CI runners.

- run-agent.mjs default: 50min → 70min
- Scan phase explicit QWEN_TIMEOUT_MS: 65min (job limit 75min)
- Fix phase explicit QWEN_TIMEOUT_MS: 75min (job limit 90min)

* fix(ci): use full SHAs in gate rebase and document typecheck-only revert

* fix(ci): skip push when verification reverts every hygiene commit

* fix(ci): route dropped and reverted findings into reportOnly

* fix(ci): correlate dropped commits to findings by bracketed id

* fix(ci): label hygiene PRs with autofix/repo-hygiene

* docs(skill): correct scan partition facts and skip empty report-only sentinel

* fix(skill): stop requiring report-only.md as a run-agent output

* fix(skill): settle the run-agent promise when the log stream errors

* fix(ci): gate the fix job on a scan-phase failure.md and validate findings JSON

* fix(ci): make the push gate's success dependency explicit

* fix(ci): keep dry runs from filing real report-only issues

* fix(ci): drop unregistered read_many_files and grant git clean for reverts

* fix(ci): count only production files and lines in the size gate

* perf(ci): skip the scan phase while a hygiene PR is still open

* docs(skill): resolve failure-handling and schema-regen contradictions

* fix(ci): apply review fixes to hygiene gates, runner signals, and skill docs

* docs(skill): align scan paths, reportOnly schema, and drop dispositions

- Document intentionally excluded packages in scan.md
- Fix cli/ui subpackage paths to include components/ prefix
- Add optional status field to reportOnly schema in SKILL.md
- Carry status in workflow gate/verify node scripts
- Give fix.md step 4b the same reportOnly drop path as 4a/4c

* fix(ci): add fork guard to repo-hygiene dedup job (#7908)

* fix(ci): surface surviving hygiene findings when verification fails (#7908)

A post-typecheck failure (build/lint/settings-schema/test) exited the verify
step non-zero without moving the surviving committed findings to reportOnly,
so the consolidated report-only issue never listed them and a persistent
failure silently discarded the same findings every week. Mirror the revert
path: an ERR trap now moves every remaining fix to reportOnly (status
failed-verify) before the step exits non-zero.

Also resolve a Scope Limits contradiction in the skill docs: the <=20-line
per-fix rule read as a hard cap while scan.md and the workflow gate enforce
only the 100-line/3-file report-only threshold. Make <=20 an explicit target
and document the new failed-verify status.

* fix(ci): salvage committed findings on failure, append to existing report-only issue, and incremental parallel scan writes (#7908)

* fix(ci): address review — contracts gate, sandbox image, gate salvage, revert labeling (#7908)

* fix(ci): enforce shell allowlist via auto-edit and salvage rebase conflicts (#7908)

* fix(ci): address review — scan mkdir allowlist, rg sandbox note, write deny rules (#7908)

* fix(ci): deny write_file to .git and persistence vectors in fix agent (#7908)

* fix(ci): deny write_file to executable configs and node_modules in fix agent (#7908)

* fix(ci): sandbox verification execution and isolate the PAT-bearing push (#7908)

* fix(ci): clarify verification sandbox flags harden, not mirror, the CLI defaults (#7908)

* fix(ci): sandbox resolve-owning-packages, re-stage gates, deny lockfile writes (#7908)

* fix(ci): make hygiene graceful-failure reachable, gate findings schema, scope artifacts (#7908)

* fix(ci): disable core.fsmonitor in gate, deny vite.config writes, align scanner wording (#7908)

* test(ci): add a repo-hygiene workflow test suite (#7908)

* fix(ci): match test assertions to workflow YAML quoting style (#7908)

* fix(ci): use double-quoted YAML if-conditions to match test assertions (#7908)

* fix(ci): mount hygiene WORKDIR read-only during sandboxed verification (#7908)

---------

Co-authored-by: 俊良 <zzj542558@alibaba-inc.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-02 09:45:38 +00:00
易良
c0196b4226
fix(ci): reconcile ECS runner updater on workflow changes (#8373) 2026-08-02 09:25:17 +00:00
Shaojin Wen
f5b0a85dcf
ci: auto-update ECS runners on stable publish and harden update job (#8343)
* ci: dispatch npm-published on stable release and harden runner update

The update-ecs-runner-qwen workflow declares a repository_dispatch
npm-published trigger, but nothing in the repo ever sent that event,
so the self-hosted ECS runners only picked up new qwen releases when
someone remembered to run the update workflow by hand. Emit the
dispatch from the release workflow after a stable (npm_tag=latest)
publish, carrying the released version; dispatch failure only warns so
an already-published release cannot be failed by it.

Also harden the update job against npm ENOTEMPTY rename failures: all
runner processes of a region share one machine, so a concurrent global
npm install from another job can race the npm rename of the package
dir. Clear stale npm trash dirs and retry up to three times with
backoff.

* ci(triage): pin action qwen reinstall to the installed version

The triage job's qwen-code-action runs an unconditional global
npm install with --prefer-offline on every run. On the shared
self-hosted ECS box that resolves the latest dist-tag from the
persistent npm cache, which lags npm publishes: after the runners
were updated to 0.21.3, the next triage job resolved latest as the
cached 0.21.2 and downgraded the box, which then also re-created the
stale npm trash dir that blocks the update workflow with ENOTEMPTY.

Capture the version the Ensure qwen CLI step verified and pass it as
qwen_cli_version, so the action's redundant reinstall targets the
exact installed version (no dist-tag resolution) and can never
downgrade the shared runner.

* ci: harden npm-published dispatch and runner update retry (#8343)

* test(ci): scope workflow assertions and add triage version-pin test (#8343)

* test(ci): pin retry-loop structure in runner update workflow test (#8343)

---------

Co-authored-by: qwen-code-ci-bot <qwen-code-ci@service.alibaba.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
2026-08-02 03:33:52 +00:00
易良
7100ffb816
ci: gate merges on deterministic no-AK E2E (#8313)
* test(sdk-typescript): stabilize permission mode round trips

* test(sdk-typescript): make permission control deterministic

* test(sdk-typescript): make orchestration E2E deterministic

* test(sdk-typescript): make orchestration E2E deterministic

* ci: gate merges on deterministic no-AK E2E

* ci: gate merges on deterministic no-AK E2E

* Merge PR #8302 review follow-up

* test(sdk-typescript): distinguish plan-mode enforcement

* test(sdk-typescript): distinguish plan-mode enforcement

* ci: validate stacked gate against main

* ci: validate stacked gate against main

* test(sdk-typescript): reuse fake tool server

* test(sdk-typescript): reuse fake tool server

* test(sdk-typescript): stabilize model switch timing

* test(sdk-typescript): ignore E2E side queries

* fix(ci): enforce no-AK gate invariants

* test(ci): cover no-AK gate event wiring

* fix(ci): isolate no-AK gate environment

* test(ci): anchor credential isolation checks

* fix(ci): complete no-AK gate credential clearing and restore closed-query tests (#8313)

* fix(ci): harden no-AK gate per review feedback (#8313)

* test(ci): assert timeout-minutes on no-AK gate step (#8313)

---------

Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-08-02 02:21:14 +00:00
Shaojin Wen
46a16de5c6
fix(autofix): state the primary agent budget and use the step's headroom (#8257)
* fix(autofix): budget the whole round, not just the agent step

The primary attempt took run-agent.mjs's 50-minute default while its step
capped at 80, so a third of that step was unreachable and every "ran out
of time" round ended for a reason this file never named. Raising it
surfaced the larger problem: the budget was never checked as a whole.

Measured on one scan (run 30646547838): setup 5-7m in earlier steps, so
it never competes with the agent step; Triage and address 50m03s on #8005
round 9 (its own timer) and 12m45s on #8211; the Verification gate 22m48s
on #8211 — the largest consumer in the job, and unbounded; push, report
and finalize 3-4s. The old arithmetic ("80 + 20 leaves ~50 for setup, two
verification passes, and reporting") predates that measurement: the real
worst case was 7 + 80 + 23 + 20 + 23 = 153 against a 150-minute job, and
a JOB timeout cancels the always() reporters, which is the silent round
the design exists to prevent.

Sized for the PRs that actually exhaust the budget (47 and 35 changed
files): agent 120m under a 130m step cap, each verification gate bounded
at 60m — 2.6x the measured pass, and a graceful degrade because both
gates already carry continue-on-error, so a bound turns a job kill into
the ordinary verification-failure path that reports. Worst case
7 + 130 + 60 + 20 + 60 + 3 = 280 against a 300-minute job, itself under
the 360-minute ceiling ubuntu-latest imposes regardless.

Raising the budget does not make a doomed round cheaper — each exhausted
budget still pushes nothing, which is why TIMEOUT_WINDOW_CAP stops a PR
after three in one window. It buys rounds that were close to finishing.

The test now asserts the SUM against the job cap and the count of bounded
steps, because asserting the numbers individually is exactly what let
153-against-150 pass. Mutations checked: budget over its step cap, either
verification bound removed, job cap back to 150, job cap over the runner
ceiling, and continue-on-error dropped are each caught.

* fix(autofix): bound every long step and cap the timeout override (#8257)

* fix(autofix): enforce the timeout ceiling and trim review feedback (#8257)

* fix(autofix): force base-10 clamp and align stale bound with job cap (#8257)

* fix(autofix): close the clamp int64 escape and pin it with a bash replay (#8257)

* fix(autofix): give the timeout clamp a floor, not only a ceiling

The review's Finding 1: the guard clamped only the ceiling, and the
uncovered side is the likelier typo. Every comment in this file, the PR
body and the operator message speak in MINUTES; this one variable wants
MILLISECONDS. A maintainer told to "raise the agent time budget" who sets
QWEN_AUTOFIX_TIMEOUT_MS=120 armed a 120 ms timer — every round SIGTERMs
instantly, writes agent-timeout, and reports "ran out of time (timeout
(120ms))" until TIMEOUT_WINDOW_CAP trips and AutoFix stops on the PR,
advising the human to raise the budget they just raised. No warning
anywhere in that loop, which is the exact misreport the clamp exists to
prevent, reached from the other direction.

A 60000 ms floor rejects every minutes-shaped value, and it also closes
the `0`/`000` hole the review noted alongside it — those passed the bare
regex while the message asserted the value had to be positive. The
message now names the units, because a units confusion is the whole
failure mode.

Replayed the review's own table against the extracted block, stdout and
stderr separated: 7200000, 3600000 and the floor itself pass untouched;
120, 60, 0, 000 and 59999 all clamp with a warning, alongside the
over-cap, malformed, octal and int64 cases the previous round closed.
The test pins both boundaries from each side (59999 clamps, 60001 does
not) and asserts the warning names MILLISECONDS.

---------

Co-authored-by: verify <verify@local>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen Autofix <qwen-autofix@users.noreply.github.com>
2026-08-01 09:23:09 +00:00
Shaojin Wen
cd88149145
feat(verify): check the accepting end, and separate empty from unmeasured (#8295)
Three rules from maintainer rounds on #8132 and #8261, chosen over the
larger set those rounds produced because this file is already 2.4x its
size six days ago and nothing gates its growth.

A capability has two ends. #8132 found a cookie-to-Authorization bridge
gated to the desktop shell where it was minted and mounted
unconditionally where it was accepted, so every server treated that
cookie as a bearer. The tell generalises: the tests were named after the
gated end, which is what made the ungated end look covered.

"Nothing found" and "could not measure" must be different values, and
the damage is done by the consumer rather than the flag. #8261 set
`emptyDiff` both for a genuinely empty PR and for a diff capture that
FAILED, and the skill answered that flag by recommending the PR be closed
as superseded — a transient fetch error closing live work. The verdict
contract already applies this rule to our own report; the code under test
gets it too.

A validity control must run before the artifact it invalidates is built.
#8261's re-classifier demoted findings from a dead harness after the
findings list was assembled, so a harness proven dead still filed
`mutant-survived` against the author. A control that runs late is not a
weaker control, it is not a control.

The review also asked for two overlapping pairs to be consolidated. One
resolved itself when the observability-ranking bullet moved next to the
concurrency rule it cites. The other I deliberately left, with a note
saying why: merging the type-boundary bullet into the sibling-sweep
bullet would edit the one rule in this file with a measured before/after
behind it, and that text is byte-identical to what the treatment arm read
(verified against the staged copy). Editing the instrument is a change to
make with a fresh measurement, not on the way past.

Each new assertion was mutation-checked: neutering any of the three
pinned phrases turns the suite red.

Co-authored-by: verify <verify@local>
2026-08-01 08:13:54 +00:00
Shaojin Wen
ce8eb830d7
fix(triage): say what the re-run summary measured, in both languages (#8273)
* fix(triage): say what the re-run summary measured, in both languages

The re-run summary told maintainers the bot had "no review of its own"
whenever it had not VOTED. On #7948 and #8141 the bot had reviewed and
deliberately deferred, leaving a COMMENTED review visible on the page —
so the comment said the opposite of what the reader could see. It was
also the one composed body in this workflow that shipped English only,
sitting directly under the bot's own bilingual stage-3 review.

Classify the head-commit state three ways instead of two and name what is
actually there. A COMMENTED-only review now reads as "carries no vote"
and lists both things that produce it: the skill deferring on purpose at
3/5 (a fork `refactor` on the approval guardrail, or a core change
escalated for maintainer awareness) or an approval a push dismissed. The
stage-3 comment says which; the reviews API cannot.

That indistinguishability is why the warning still fires on both. My
first pass silenced the defer case as routine, which would have muted the
guard for the incident the existing test encodes — a dismissed approval
leaving only a COMMENTED behind. Either way the PR is one approval short
with the bot not supplying it, so the operator signal stays and only the
wording differs. DISMISSED and PENDING are explicitly not deferrals: a
push voids the bot's approval, and that is precisely when a fresh one is
required.

Verified by running the extracted step under its own `set -euo pipefail`
with a stubbed gh: the real review payloads from #7948 and #8141 both
classify as deferred; ten synthetic arms separate own / deferred / none,
including human-approval-only and a vote on an older commit, which must
stay `none`. The composed body was rendered through GitHub's own
`POST /markdown` — 8 code spans, 6 bold runs, 2 links, no live mentions,
Chinese intact.

* fix(triage): address review feedback on re-run summary tests (#8273)

---------

Co-authored-by: verify <verify@local>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
2026-08-01 07:22:08 +00:00
Shaojin Wen
bc382c3ff9
feat(verify): sweep sibling shapes, calibrate replays, measure suggested fixes (#8242)
* feat(verify): sweep sibling shapes, calibrate replays, measure suggested fixes

The verify-pr skill produced a `merge-ready` report on a head that leaks
live HTML. Measured on #8147 at b0ae340b with two control arms and one
treatment arm (real qwen3.8-max-preview, isolated QWEN_HOME each, arms
differing only by this file): both control arms concluded `merge-ready`,
the treatment arm concluded `findings` and shipped a measured two-line
fix.

The decisive arm is the control replicate. It spent the same budget as
the treatment arm and built a stronger instrument — a micromark+GFM
rendering oracle, 23 hostile fixtures, 254 assertions. It built the
HTML-block-times-fence fixture and separately probed code spans, and
never crossed the two axes. Re-running that arm's own oracle on the
fixture it never built renders a live <img> and a live <a href>, so it
would have failed its own guarantee-1 assertion. The miss was which
fixtures the agent thought to construct, which is what these rules
supply.

Added to the A/B method: sweep the sibling shapes a parser-class fix
does not pin; treat untrusted text reaching a parser as a scaling
question and probe it with a timed ladder; audit a stated cost against
the repo's own accounting of the same mechanism; enumerate the unnamed
siblings of an accepted-tradeoff list. Added to shared-state probing: an
instruction in a prompt is not an invariant, and interleavings that
fabricate a result outrank ones that crash. Added to vacuity: run the
mutation in reverse (a suite green with and without a candidate fix pins
nothing along that axis), and read a test's name as a claim about its
fixture. Workflow replays now calibrate against a real production
artifact before their cells are believed, and suggested fixes ship with
collateral measured. The follow-up status enum gains `worsened`, because
a fix can move an accepted tradeoff.

Rules whose effect was not measured are marked as such in the PR
description rather than presented as validated.

* feat(verify): read reports as coverage specs, prove negatives by census

Six more rules, each taken from a hands-on verification round that found
something the skill could not have produced.

From #8037, a fallback parser for XML tool calls: a bug report enumerates
its own cases, and those names are coverage claims the fix inherits —
holding the issue's reported 1,898-character preamble fixed and varying
only the tool showed the guard declining run_shell_command, which the
issue names explicitly, so the fix covered half of what it was filed
against. The same round found one defect with a loud variant (schema
rejects a dropped argument) and a silent one (a truncated file), which
fixes the ordering: rank variants by observability, not blast radius —
the same ordering the concurrency rule already uses. And its harness
replayed the reported wire bytes without reproducing the model-side
degradation that produces them, so Not covered now asks which of the two
a reproduction actually has.

From #8005, a Goal runtime driven through a real TUI: walking the PR's
own Reviewer Test Plan step by step turned an unrunnable step into the
round's sharpest finding, because no code path could insert real user
input into an active turn and the feature's completion criterion needed
exactly that. Proving it took a census rather than a reading — 30
captured verifier payloads carried one evidence kind and never the
required one. And its mutation matrix is the reason survivors are now
gated on a positive control: two mutations survived 429/429 and 326/326,
believable only because a third, expected-caught mutation turned one test
red.

None of these six were measured with an A/B on this file; they are drawn
from rounds that found real defects. The PR description says so.

* fix(verify-pr): address review — calibration fallback, timeout cap, pinning, section placement (#8242)

* fix(scripts): pin bare words in skill assertions, not markdown emphasis markers (#8242)

* fix(verify-pr): address round-3 review — pin payload words, bind timeout, relocate misplaced bullets (#8242)

---------

Co-authored-by: verify <verify@local>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>
2026-08-01 06:00:14 +00:00
Shaojin Wen
0b36e597d4
fix(triage): render the verify report as sanitized markdown, not an escaped pre dump (#8147)
* fix(triage): render the verify report as sanitized markdown, not an escaped pre dump

The sandboxed-verification comment embedded report.md inside
<details><pre><code> with full HTML escaping. Safe, but unreadable:
the report is a curated bilingual document — tables, headings, nested
<details> folds — and it displayed as a wall of raw markdown source
(#8140's verify comment was the exhibit: literal asterisks, table
pipes, and <details> tags shown as text).

report.md now renders as markdown through emit_report, which holds the
same security floor with four line-independent guarantees: every & < >
is escaped and only the structural tags the report legitimately uses
(details/summary/pre/code/br) are un-escaped back to live tags, so no
other tag can form; the comment-open token is broken (the
autofix-proven neutralizer), so no forged qwen-triage:* marker can
appear in the raw body the upsert logic greps; @ becomes &#64;, which
renders identically but can never fire a mention; and unbalanced
<details> opens are counted and closed, so a malformed report cannot
swallow the footer. An oversized report falls back to the escaped-pre
embedding wholesale (truncated markdown dangles fences and folds), as
does any sanitizer failure. The tmux lane's raw-log embedding is
untouched — escaped pre remains right for logs.

The zero-match grep in the fold balancer carries || true: under the
step's pipefail, a report with no folds would otherwise kill the whole
composer.

Tests: a behavioral replay drives the real emit_report — structure
survives (tables, folds, no pre/code), the security floor holds (no
live marker/mention/tag, entities escaped, folds balanced), and the
oversize fallback produces the escaped shape; the full-render ordering
pin follows the new heading.

* fix(triage): cap sanitized report size and use portable ERE sed (#8147)

* fix(triage): budget fold-closer overhead against the report size cap (#8147)

* fix(triage): annotate emit_report fallbacks with distinct warnings (#8147)

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

* fix(triage): sanitize the verify report code-region-aware and re-collapse it (#8147)

Replace the unconditional sed escaping in emit_report with a node
sanitizer that tells code regions apart from prose. CommonMark does not
decode entities in code spans/fences, so escaping & < > @ there showed
&amp;&amp; / &lt;T&gt; / &#64;pkg in the commands, types, and paths a
report is read to copy. Prose is still escaped (< only; & and > are not
security controls and mangling them killed && and blockquotes), code is
left inert, the <!-- break stays global so no forged marker survives in
the raw body the upsert greps, and folds are balanced over prose only so
a fenced </details> can no longer defeat guarantee 4 (surplus closers
dropped, unclosed opens closed).

Wrap the rendered report in a collapsed <details> so it costs one line
again instead of expanding up to 45 KB inline, narrow the tag allowlist
to details/summary, bound the whole wrapped section against the size cap,
and make every fallback label say "truncated".

* fix(triage): close dangling code fences at EOF and test the sanitize-failure fallback (#8147)

* fix(triage): defuse mentions with ZWSP and track HTML blocks in sanitizer (#8147)

* fix(triage): prose-escape code spans inside HTML blocks and widen inHtml entry (#8147)

* fix(triage): degrade to escaped fallback when a code fence is open at EOF (#8147)

* fix(triage): fail closed on paragraph code-span and container-fence divergence (#8147)

* fix(triage): fail closed on escaped-backtick and entity-forgery sanitizer holes (#8147)

---------

Co-authored-by: verify <verify@local>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-01 05:25:09 +00:00
易良
eabf312a06
feat(autofix): unify local and CI flows in one skill (#8121)
* feat(core): add current PR autofix controls

* fix(core): align autofix ci status wording

* feat(core): add current PR Autofix watcher

* feat(cli): route Autofix watcher ticks

* fix(autofix): fail closed on invalid watchers

* fix(acp): reject malformed autofix ticks

* fix(autofix): stop malformed watcher jobs

* fix(autofix): preserve unrelated cron jobs

* fix(autofix): separate watcher command from workflow skill

* test(autofix): cover headless watcher delivery

* test(autofix): cover watcher safety edges

* test(autofix): cover watcher failure paths

* fix(autofix): preserve non-watcher cron jobs

* fix(cli): fail closed on malformed autofix ticks

* test(cli): type autofix child process mock

* test(autofix): align malformed watcher coverage

* test(autofix): assert detached head skips gh

* fix(autofix): close watcher control gaps

* fix(autofix): preserve ordinary cron queue semantics

* fix(autofix): extract shared constants, validate job id, broaden off filter

* test(autofix): cover malformed watcher cleanup

* refactor(autofix): keep current PR controls minimal

* feat(autofix): reuse project skill for local runs

* fix(autofix): harden local review boundaries

* fix(autofix): enforce nested review containment

* fix(autofix): require local repository trust
2026-08-01 02:40:25 +00:00
Shaojin Wen
e447cbe0dd
feat(triage): start the verify lane alongside /triage on a pull request (#8249)
A `@qwen-code /triage` comment on a PR now starts the sandboxed verify
lane too, in parallel. The two answer different questions — triage reads,
verify builds and runs — and neither waits on the other; they were
already sibling jobs on `needs: [authorize]`, so this is a trigger
change, not a new pipeline.

The trigger cannot be a second copy of the command patterns. authorize
publishes `verify_lane`, and the verify job's `if` and concurrency group
both read it, because re-matching the strings in the job predicate is
exactly what would let the trigger and the trust classification drift:
the classifier keyed on `/verify` while the predicate answered to
`/triage` as well, and a `/triage`-started run would then have executed
an external author's code with `verify_trust` empty — skipping the
head-OID pin, the risk screen and both workspace wipes, the four controls
that exist for untrusted code on a reused pool. Routing the new trigger
through the same classifier is what keeps them on.

The lane fails closed differently from an explicit `/verify`. An
unreadable author permission or a failed head-OID snapshot still denies
`/verify` outright, since the commenter asked for exactly that; on
`/triage` it closes the lane only, so a flaky permission API cannot cost
a reviewer their triage. Automatic per-PR triage (`pull_request_target`)
deliberately does not pull the lane: the constraint is the agent budget,
not runner capacity — a verify run is up to 110 minutes of model time,
and every `synchronize` would spend one. Commenting is what says the PR
is worth it. On a plain issue the lane stays off and no author lookup is
spent.

The verify lane's trigger turned out to be unpinned: deleting the gate
from the job predicate left the suite green. The new test pins both
predicates, and it took two tries — asserting over the whole job text
could not tell `if:` from `concurrency:`, so the first version survived
its own mutation, and so did the positive control written to vouch for
it. Both are now sliced per predicate. Mutation results: dropping the
gate from `if`, from `concurrency`, from authorize's `/triage` branch,
and making the piggybacked lane deny triage are all caught; the
unmutated control and the ECS kill-switch control behave as expected.

Verified by executing the real authorize step with a per-user permission
stub across twelve branches: /verify and /triage on a PR classify
identically (trusted for a write author, external plus a pinned OID
otherwise), /verify still denies on a lookup failure where /triage keeps
should_run with the lane off, /tmux and plain comments are unchanged, and
a commenter without write is denied throughout. actionlint, yamllint,
eslint clean; the prettier warning on this file is pre-existing (fails
identically with the change stashed).

Co-authored-by: verify <verify@local>
2026-07-31 17:33:25 +00:00
Shaojin Wen
2ad15a9fbc
fix(autofix): Extend suggestion handling to ten rounds (#8247)
* fix(autofix): extend takeover suggestion window

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

* fix(autofix): use ten-round suggestion threshold

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-31 17:20:28 +00:00
Shaojin Wen
8efdf749ad
fix(autofix): guard review thread resolution (#8231)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-07-31 16:04:30 +00:00
qwen-code-dev-bot
0d3c8641f1
ci: cache npm downloads for verify and tmux build steps (#7885)
* ci: cache npm downloads for verify and tmux build steps

The "Install and build PR app" step in both the verify and tmux
jobs runs `npm ci` from scratch every time, taking ~5m40s out of
a 15-minute verify run. Add an `actions/cache@v4` step before each
build that restores the npm download cache keyed by
`package-lock.json` hash.

Security model: the cache restore runs as root (with full Actions
credentials) in a separate step. The build step itself still strips
ACTIONS_RUNTIME_TOKEN/URL/CACHE_URL before running PR lifecycle
scripts as the `node` user, so untrusted code cannot read or write
the Actions cache. The restored cache directory is chowned to
`node:node` and passed via `npm ci --cache` so the build user reads
packages from the local cache without touching the cache API.

Expected improvement: npm ci drops from ~4min to ~1min on cache
hit, cutting total verify time from ~15min to ~10min.

* ci: pin actions/cache to SHA for supply-chain security (#7885)

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

* ci: add npm cache comment to verify job matching tmux job (#7885)

* fix(ci): use actions/cache/restore to prevent cache writes from PR code (#7885)

* test(ci): pin actions/cache/restore as restore-only invariant in both lanes (#7885)

* test(ci): assert npm ci consumes the restored cache directory (#7885)

* fix(ci): align prepare log with npm ci cache flag and harden cache tests (#7885)

* fix(ci): harden npm cache tests and document missing save step (#7885)

* fix(ci): add npm cache producer and clear stale cache before restore (#7885)

* test(ci): harden npm cache guards per review (#7885)

* fix(ci): make npm cache test robust to prettier YAML quoting (#7885)

Prettier reformats the hashFiles() key value from single-quoted YAML
(with '' escaping) to double-quoted, breaking the raw-string comparison
in the cache producer test. Compare parsed scalar values instead.

* fix(ci): run npm cache producer on the consumer runner so restores hit (#7885)

actions/cache scopes an entry by a hash of the literal cache path plus the compression method. The producer ran on ubuntu-latest (host path, zstd) while the verify/tmux consumers run in a node:22-bookworm container (container path, gzip), so the versions never matched and every restore was a guaranteed permanent miss. Move the producer onto the same runs-on + container so path and compression match by construction, give the restore step an id and report cache-hit to the job summary so any future miss is visible, and point the stale-cache clear step at $RUNNER_TEMP so it removes the container path rather than the inert host path.

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>
Co-authored-by: Qwen Code CI <qwen-code-ci@users.noreply.github.com>
2026-07-31 15:20:08 +00:00
ytahdn
7918717ade
fix(ci): avoid verify capture color conflict (#8236)
Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com>
Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com>
2026-07-31 14:15:40 +00:00
Shaojin Wen
f66bfaad57
fix(release): keep notes anchored and cap the release body (#8199)
* fix(release): keep notes anchored and cap the release body

The v0.21.2 publish failed at "Create GitHub Release and Tag" with
HTTP 422 "body is too long (maximum is 125000 characters)", after every
npm package had already been published.

Stable releases are tagged on their own release/* branch and merged back
to main only afterwards, so the previous stable tag is never an ancestor
of the branch being released. The ancestor guard therefore dropped
--notes-start-tag on every stable release, and without an anchor GitHub
generates notes across the entire branch history (8000+ commits), which
overruns the body limit.

Always pass the previous tag instead: GitHub diffs it through the merge
base, which is how v0.21.1 produced a 27KB body from a tag that was
equally divergent. Generate the body through the generate-notes API
first so an oversized changelog is truncated on a UTF-8 boundary, and
degrade to an unanchored body and then a minimal one, rather than
aborting a release whose packages are already on npm.

* test(release): pin the anchored release-notes contract

The workflow test asserted the ancestor guard that dropped
--notes-start-tag on every stable release. Assert the replacement
instead: the previous tag is always passed to generate-notes, the body
is capped, and ancestry no longer decides whether notes are anchored.

* refactor(release): extract release-notes capping into a tested helper

The degradation chain lived inline in the workflow bash, so nothing
pinned that a capped body plus its footer stays under GitHub's 125000
character limit, that truncation never splits a multi-byte character, or
that the chain always yields a non-empty body. Move it to
.github/scripts/cap-release-notes.mjs with a collocated node:test suite,
matching the other workflow helpers.

Capping on code points rather than bytes drops the head/iconv dance and
makes the surrogate-pair case testable. The helper also absorbs the
empty-body fallback, which caught a real defect: gh writes the API error
payload to stdout when generate-notes fails, so a doubly failed call
would have published `{"message":"Not Found",...}` as the release body.
Discard a failed attempt's output instead.

* test(release): exercise the surrogate-pair cut and footer-overflow branch (#8199)

---------

Co-authored-by: Qwen Code Bot <qwen-code-bot@alibabacloud.com>
2026-07-31 09:55:38 +00:00
Shaojin Wen
3ff8892a82
feat(ci): fail the startup bundle check when the CLI entry is hoisted into a chunk (#8203)
`packages/cli/src/cli.ts` is the esbuild entry point and bootstraps only under a
main-module guard:

    if (process.argv[1] !== undefined &&
        import.meta.url === pathToFileURL(process.argv[1]).href) {
      void runCliEntryPoint();
    }

The bundle is built with `splitting: true`. If any module the entry loads lazily
(e.g. `gemini.tsx`, reached through `await import('./gemini.js')`) adds a static
`import ... from './cli.js'`, esbuild moves the entry module's body into a shared
chunk and leaves `dist/cli.js` as a re-export stub. Inside a chunk
`import.meta.url` is the chunk's own URL, so the guard never matches and the
bundled CLI exits 0 without running anything.

Nothing catches that today: tsc, eslint and every src-based unit test stay green,
because the breakage only exists in the bundle. The single CI step that executes
`dist/cli.js` is the no-AK integration smoke test, which reports it as
`daemon exited with 0 before listening` from three unrelated serve suites — a
symptom that points nowhere near the import that caused it.

Assert instead that the entry output still compiles the entry module. When the
entry is hoisted, `dist/cli.js` keeps no inputs of its own, so the metafile the
existing closure checks already read is a precise signal, and the diagnostic can
name both the cause and the fix.

Co-authored-by: verify <verify@local>
2026-07-31 08:57:57 +00:00
Shaojin Wen
0d6f3d42e6
feat(verify-pr): ship a one-command capture helper (#8114)
* feat(verify-pr): ship a one-command capture helper

Fourth attempt at the same failure, and the first one aimed at the real
cause. The score so far:

  #8016  captures were "Optionally … when text cannot carry the
         oracle"                                        -> 0 images / 14 runs
  #8104  captures became budget item 4 in Scope
         selection                                      -> 0 images / 1 run

That last run is the one that settles it. Same PR (#7975), browser
installed and working ("Install evidence browser: success"), and the
verification got DEEPER — 64 assertions against 40, 53 tables against
31 — while still producing zero images. An agent reading the instruction,
doing more work than before, and still not capturing is not an agent that
missed the instruction.

The cause is one I should have checked when I wrote #8016: the skill sent
the agent to build node-pty -> xterm.js -> Playwright itself, and
`node-pty` is not a dependency of this repo. It needs a native build. The
`playwright` package is not a declared dependency either. So the
documented route did not exist, and the incentive was entirely against
trying it: authoring that pipeline risks failing and eats budget, while
skipping costs nothing and is invisible.

`scripts/verify-capture.mjs` makes a capture one command using deps that
are already installed:

    node scripts/verify-capture.mjs --out evidence/01-ab.png \
      --title 'A/B: the gate flips' -- node my-harness.mjs

Command (or stdin) -> @xterm/headless parses the ANSI into a cell grid
with colour and bold -> SVG -> sharp rasterises. No browser, no
pseudo-terminal. A non-zero exit from the captured command still produces
an image, because capturing a failing base arm is the normal case.

The skill's dead route is removed and replaced with that command, and the
budget line drops from ~5 minutes to ~2 because there is no pipeline to
author. QWEN_VERIFY_CHROMIUM and its install stay for a future web-UI
capture, but the terminal route no longer depends on them — gating on a
browser the route does not use is how an absent browser turns into a
skipped capture.

Mutation-verified 7/7 against the real helper and the real PNGs: bare LF
(staircase render), no event-loop turn (blank capture), dropped bold,
dropped colour, no blank-row trimming, tolerating an empty capture, and
dropped geometry validation.

Two of those initially SURVIVED. The colour test compared "coloured and
bold" against plain and asserted the bytes differ — which passes while
EITHER attribute survives. That is the wrong-reason trap this skill warns
about, met in the skill's own test file. Each attribute is now isolated
against the same plain baseline (green-no-bold, bold-no-colour), and both
mutations kill.

119/119 across both suites; prettier and eslint clean.

* fix(verify-pr): harden capture helper per review (#8114)

- Strip U+FE0F before rasterising: the emoji variation selector made Pango
  abort() in native code (SIGTRAP, no PNG, no diagnostic) when no colour-emoji
  font exists; the base codepoint renders. Add a non-ASCII regression test.
- Await xterm's write callback instead of a fixed 120ms sleep, so a large
  capture is not read mid-parse and silently come out blank.
- Name a signal-killed child ("killed by SIGKILL") rather than "exited null".
- Warn on stderr when input is taller than --rows and the top is dropped.
- Correct the falsified "route did not exist" rationale: the browser pipeline's
  deps do resolve from this repo; the real fragility is that
  integration-tests/terminal-capture is not a root workspace. Keep a skill
  pointer to terminal-capture for TUI/web-UI captures this helper cannot do.
- Qualify the colour claim (16 base ANSI colours; 256/truecolor fall back).
- Tests: feed both --cols and --rows to the geometry guard, exercise escapeXml,
  flatten the SKILL.md assertion against reflow, and replace the platform-fragile
  PNG byte-length check (flaked at 846B on Linux vs >1000B on macOS) with the
  deterministic canvas geometry.

* fix(verify-pr): exercise colour fallback and fix wrap-aware truncation warning (#8114)

* fix(verify-pr): separate stdout/stderr join and pin next() guard (#8114)

* fix(scripts): correct verify-capture truncation guard and SGR 30 colour (#8114)

The truncation guard compared wrapped rows against --rows, but
newline-terminated output needs one row beyond its last line (the final
CRLF scrolls it off the scrollback-less viewport), so input of exactly
--rows lines — including the default 40 — lost its top line with no
warning, and taller input under-reported the drop by one. Compare against
a capacity of rows - 1 for newline-terminated input.

Also lift SGR 30 foreground to the default grey: it mapped to #1e1e1e,
identical to the canvas background, so black-foreground labels (e.g.
vitest's project badge) vanished as black-on-black.

* fix(scripts): address review feedback on verify-capture helper (#8114)

- Declare sharp as a root devDependency so the script does not rely on
  workspace hoisting from packages/core
- Guard against TTY stdin hanging silently: check process.stdin.isTTY
  before readFileSync(0) and print usage immediately
- Fix phantom blank row when stdout already ends with a newline: only
  insert a separator between stdout and stderr when stdout lacks a
  trailing newline
- Strengthen 256-colour/truecolor test to decode pixels and assert the
  #d4d4d4 fallback grey is present
- Add test for non-newline-terminated input that fits exactly --rows
- Add test for the phantom blank row fix (console.log + stderr)

---------

Co-authored-by: wenshao <wenshao@example.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-07-31 07:48:09 +00:00
Shaojin Wen
cc508dd7ba
fix(autofix): salvage race-lost pushes by merging the moved head and retrying (#8042)
* fix(autofix): salvage race-lost pushes by merging the moved head and retrying

The review-address push is one-shot: when anything pushes to the PR head
during the agent's ~50-minute window, the final push dies 'fetch first'
and the entire verified agent run is discarded. The per-PR head-write
concurrency group cannot prevent this — it serialises this repo's
workflows, not the PR author or the fork side. Observed twice in one day
(#7983 after a 56-minute run, #7985 after 43 minutes).

On rejection, fetch the moved head, merge it into the local line, and
retry (bounded at 3 attempts). Merge rather than rebase: the agent's own
conflict-resolution rounds create merge commits, and a rebase would
flatten them and can silently re-introduce the conflicts they resolved.
The merge result descends from the remote head, so the retried push is a
fast-forward and rewrites nothing. A genuine content conflict aborts the
merge and falls through to the existing failure path unchanged.

When a salvage merge happened, the round report discloses that the
round's verification predates the merge so mid-run commits get
re-checked by a human.

* fix(autofix): address salvage-loop review findings

- Gate the PUSH_RACE_MERGED disclosure on HEAD actually advancing: a
  transient push failure (upload timeout, 503) on an unmoved branch
  no-ops the merge ('Already up to date') and must not tell the
  reviewer to re-check mid-run commits that never existed.
- Annotate the salvage fetch failure with ::error:: like the two
  adjacent failure paths, so a deleted fork branch or network error
  does not kill the step with an unannotated exit 128 under bash -e.
- Re-pin the same-repo push URL construction in tests: it lost its old
  'origin "${BRANCH}"' pin in this rework, leaving a ${REPO}→${HEAD_REPO}
  mutation (malformed remote in the same-repo case) unkillable.

* test(autofix): restore dropped mutation-killing pins and add structural assertions (#8042)

* test(autofix): pin exit 1 in the give-up guard regex to kill the deletion mutation (#8042)

* test(autofix): pin exit 1 in the fetch-failure and merge-conflict salvage paths (#8042)

* test(autofix): strengthen salvage-test pins to kill init-value and capture-order mutations (#8042)

---------

Co-authored-by: verify <verify@local>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-07-30 11:38:47 +00:00
Shaojin Wen
f6ae9202b1
feat(autofix): per-source feedback budget in Critical-only mode (#8071)
* fix(autofix): never defer maintainer feedback in Critical-only mode

Critical-only mode (after 5 change-producing rounds) classifies
feedback lexically: only a literal **[Critical]** tag or a
CHANGES_REQUESTED review survives; everything else is deferred before
the agent reads a word of it. That rule was built to stop the review
bot's suggestion ping-pong, but it catches maintainers too. Observed
four times in two days (#8037, #7944, #7885, #7799): a maintainer's
review with explicit merge-blocking findings — #8037's said 'I'd fix
before merge' on a correctness bug and a security-adjacent one — was
wholesale-deferred as one 'non-Critical item', and the bot then
reported 'No Critical feedback. The Issue-level comments sections are
empty', which was lexically true and substantively false. The bot's
own advertised definition ('correctness bugs, security issues, or
formally requested changes') is exactly what the deferred comments
contained; the agent that could have applied that definition never saw
them.

The lexical test now applies exclusively to the review bot's output:

- All three actionable filters (reviews, inline, issue-level) pass
  anything not authored by the review bot straight through in
  Critical-only mode — the agent judges maintainer feedback on
  content, as everywhere else.
- All three deferred-list builders keep only review-bot items, so a
  maintainer comment can never appear as an 'audit record'.
- The deferral note says what is actually deferred (the automated
  reviewer's non-Critical suggestions), states that maintainer
  feedback is never deferred, and names the exit (@qwen-code /retry
  opens a fresh counting window).
- SKILL.md's Critical-only policy now marks everything rendered in
  the actionable sections as in scope, so the agent does not re-refuse
  what the filter passed through.

Behavioral test updated both ways: maintainer comments/reviews stay
actionable in Critical-only mode across all three sources, bot
suggestions still defer, and structural pins hold the bypass and the
bot-only select in all six filters.

* feat(autofix): per-author feedback budget in Critical-only mode

Follow-up to the author-based split, prompted by the obvious
counterexample: a human account can host an automated reviewer loop
with the exact regeneration property the review bot has — feedback
re-generated after every push at zero marginal cost — so 'not the
bot' cannot mean 'never throttled'. An account is an accountability
unit, not a throttle; the brake has to key on measured regeneration.

Unified model: once Critical-only engages, every source has a bounded
budget of untagged feedback batches per counting window. The review
bot's budget is zero (all deferred, as before). A human's is
CRITICAL_ONLY_HUMAN_BATCHES (2) CONSUMED batches: feedback items are
bucketed into the (prev marker ts, marker ts] span that evaluated
them, only spans from Critical-only rounds count, and an author needs
K distinct consumed spans before their new untagged feedback defers.
Fresh unevaluated feedback never counts against its own author, and
the census is window-scoped, so /retry resets the budget with the
window.

The observed cases (#8037/#7944/#7885/#7799 — one or two late
verification reports each) stay fully served under K=2; a looped
reviewer is throttled after 5+K driven rounds instead of grinding to
the 100-round cap. Past the budget, continuing requires one conscious
act — **[Critical]**, a Request changes review, or /retry — which is
precisely what separates intent from automation. Over-budget authors
are named in the deferral note with those exact escapes.

Tests: the six filter replays gain over-budget cases both ways (the
tagged/CR escapes survive even over budget), and the budget census
itself is replayed over fixture files — two consumed critical-tail
batches list the author; one batch, pre-Critical batches, unconsumed
feedback, untrusted authors, and command comments never count.

* fix(autofix): fix deferred-feedback bash quoting and drop a dead jq binding (#8071)

* test(autofix): exercise census window-isolation guard with a stale-window fixture (#8071)

* test(autofix): make census command-exclusion observable; surface census stderr (#8071)

* fix(autofix): exclude never-deferrable feedback from the budget census (#8071)

The Critical-only per-author budget census counted every trusted review,
inline comment, and issue comment, including feedback the deferred renderer
would never defer: **[Critical]**-tagged comments, Request changes / APPROVED
reviews, inline replies rooted at a Critical comment, and inline comments
attached to a Request changes review. A maintainer who followed the documented
escape hatches (tag Critical, request changes) thereby spent their own budget
and had later untagged feedback silently deferred — the exact bug this PR
fixes, re-created one level down.

Mirror the three deferred-builder predicates in the census item filter so a
batch is counted only when it is actually deferrable. Extend the census replay
test with protected authors (Critical-only, Request changes, APPROVED,
Critical-rooted replies, Request-changes-review inlines, the review bot as a
trusted MEMBER, and a sentinel-ts marker probe) that each carry two
consumed-span batches yet must stay absent, so dropping any one exclusion now
fails the suite. Also fold bash's stderr into the bash -n guard assertion so a
future quoting regression reports the syntax error, not just a non-zero exit.

---------

Co-authored-by: verify <verify@local>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-07-30 11:38:40 +00:00
Shaojin Wen
4dab39c8d7
fix(autofix): answer round-cap refusals on the PR instead of only in logs (#8067)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
* fix(autofix): answer cap-gate refusals on the PR instead of only in logs

Observed on #7836: the fleet shepherd detected a merge conflict, posted
'dispatched the autofix loop to resolve it', and the dispatch died at
the scan's round-cap gate with only a log line — the PR page showed a
promise, the run showed green, and the conflict sat unhandled for
hours. Three silences stacked: the standard-management cap itself is
silent (the pause notice was takeover-only, so #7836 hit 10/10 with
zero PR-visible notice), the forced-dispatch refusal is silent, and the
shepherd dedups per head SHA — a capped PR gets no pushes, so its head
never changes and conflict handling froze permanently.

Two scan-side changes (the shepherd stays untouched — the windowed
round computation lives in the scan and duplicating it would drift):

- A FORCED dispatch (shepherd conflict lever or a human) refused at the
  cap gate now answers on the PR: cap value, what stays unhandled, and
  the two recovery commands (/retry for a fresh window, /takeover for
  the raised cap). No dedup — the shepherd sends at most one dispatch
  per head, and a human asking twice deserves two answers.
- The cap pause notice covers ALL managed PRs: the takeover variant
  keeps its wording, standard bot PRs get their own (/retry or
  /takeover). Same marker, same once-per-window dedup, same consent
  and PAT-identity checks — skip wins everywhere, and only the
  takeover variant requires the label to still be present.

After a re-arm the next scheduled scan picks the PR up normally
(conflict targets are label-independent), so the frozen-head loop
resolves without any shepherd change.

* test(autofix): replay the cap-notice consent gate across label/takeover permutations (#8067)

* fix(autofix): gate the loud cap-refusal on workflow_dispatch (#8067)

FORCED_PR is populated for every trusted pull_request_review (route emits
pr_number for those), not just workflow_dispatch, so on a capped PR each
review submission landed in the un-deduped refusal branch — 7 "Dispatch
refused" comments on #7836 where 2 carried the information. Answer only
workflow_dispatch (the shepherd lever or a human); review submissions stay
covered by the once-per-window pause notice. Adds a verbatim behavioral
replay of the guard so a dropped EVENT_NAME condition fails the test.

---------

Co-authored-by: verify <verify@local>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-07-30 09:46:45 +00:00
Shaojin Wen
b64a6c4f13
fix(verify-pr): budget evidence capture in scope selection (#8104)
Post-merge measurement of #8016, on the first two live runs after it
landed (#7975 and #8066): the browser installed successfully — "Install
evidence browser: success" in both — and both reports carried **zero
images**. #7975 has 31 tables, #8066 has 407 assertions. Neither has a
witness.

The cause is the third instance of one structural bug, and it is the
same one twice already fixed:

  #7917  the /verify recommendation sat inside a section headed
         "local invocation ONLY" -> 1/16 reach
  #8016  captures were "Optionally ... when text cannot carry the
         oracle" -> 0/14
  now    captures are a required deliverable in the ARTIFACT CONTRACT,
         but the plan the agent executes is the Scope-selection budget
         list, and that list had zero mentions of evidence, png,
         capture or screenshot

An agent that budgets by that list never allocates time for a capture,
however firmly a later section says to produce one. So captures are now
budget item 4 alongside the A/B, the harnesses and the gates, with the
time reserved (~5 min), the gate named (QWEN_VERIFY_CHROMIUM=1), the
count bounded (normally two, at most a handful), and the measured
failure stated so the rule carries its own reason.

The report structure gets the matching half: the A/B table item now says
to reference the capture of those cells by filename. A produced image
with nowhere to go is as dead as an unproduced one.

Mutation-verified 4/4: dropping the budget item, the time allowance, the
count bound, or the report-side reference each turns the test red.

Not changed, and worth stating rather than implying: the seven techniques
from #8010 do not appear in either report. I do not read that as failure
— every one of them is conditional ("when one fix bundles two changes",
"when the oracle is an instrument", "when a PR adds a writer to a shared
store") and neither PR met those conditions. #8010 is not yet falsifiable
from these runs. What both reports DO show is older skill rules working:
a naive-matcher differential in #8066, and #7975's realpath check finding
node_modules/@qwen-code/qwen-code-core resolving into the HEAD tree and
then reasoning about whether that contaminates the control.

107/107 tests; prettier and eslint clean.

Co-authored-by: wenshao <wenshao@example.com>
2026-07-30 07:50:14 +00:00
Shaojin Wen
f4cd6e1d8b
fix(ci): gate the attachment guard before it allocates a runner (#8095)
Measured on a congested pool: 88 active jobs, 72 hosted and 15
self-hosted. The self-hosted 15 were all running with zero queued; the
hosted 72 were contending, and 20 of them were the SAME job —
`remove-suspicious-attachments`, all queued, none running.

Its real cost is not the work. Recent completed runs:

    queue=629s run=5s      queue=568s run=2s
    queue=518s run=2s      queue=340s run=3s

Two to five seconds of API calls behind up to ten minutes of queueing.

And almost none of it needed to happen. The trust check lived INSIDE the
github-script, so a runner was queued, allocated and started before the
job could decide it had nothing to do. Over the 200 most recent comments
on this repo: 184 from trusted associations, 9 from bots, 7 actually
needing a scan. 96.5% of these runs existed to print "Trusted author;
skipping".

Two changes:

- Hoist the association and bot checks into the job `if:`. GitHub
  evaluates `if:` BEFORE allocating a runner, so a trusted comment now
  costs nothing. The script keeps its own copies: the gate is an
  optimisation, not the control, and the two must be able to disagree
  without becoming unsafe. Every ambiguity therefore resolves toward
  RUNNING the scan — an unrecognised payload yields an empty
  association, which is not in the trusted list, so the job runs.

- Add a per-comment concurrency group with cancel-in-progress. The
  workflow listens on `edited` as well as `created`, and the bot PATCHes
  its own comments constantly, so repeated edits of one comment stacked.
  The scan reads the comment's CURRENT body, so a queued earlier scan is
  already stale and cancelling it loses nothing. (Contrast the verify
  lane, where cancel-in-progress is deliberately false because a
  cancelled run destroys evidence.) The key falls back to run_id so an
  unexpected payload gets its own group instead of serialising every
  scan into one.

Deliberately NOT moved to the self-hosted pool, though it would fit
technically (no checkout, no PR code, API calls only): the 20 stacked
jobs were duplicates, so relocating them just fills the ECS pool
instead — and that pool is what /verify and /triage depend on. It also
holds issues:write while processing untrusted comment bodies, which
belongs on ephemeral hardware rather than reused machines.

The `if:` semantics are verified against all payload shapes — 12 cases
covering both `comment.*` and `review.*` associations, bots, and
missing/empty payloads, each asserting which direction it resolves.
CONTRIBUTOR is deliberately NOT trusted: a merged PR does not make
someone's links safe.

Mutation-verified 6/6: dropping the review payload path, dropping the
bot check, adding CONTRIBUTOR to the trusted list, turning off
cancel-in-progress, collapsing the group to a global key, and inverting
the gate so untrusted comments are the ones skipped — each turns a test
red. The last is the one that matters; it is the only mutation here that
would be a security regression rather than a cost regression.

148/148 tests across both suites; actionlint exit 0; prettier and eslint
clean.

Co-authored-by: wenshao <wenshao@example.com>
2026-07-30 06:32:55 +00:00
Shaojin Wen
6b0d2bbca0
feat(triage): make /verify evidence screenshots actually possible (#8016)
* feat(triage): make /verify evidence screenshots actually possible

Measured across the 14 real verify reports on this repo: tables appear
in 14/14 (4 to 31 rows), images in 0/14. The hosting machinery has been
complete the whole time — pr-assets branch, PNG magic check, 8 images,
2 MB cap, sanitised names — and it has never once been fed.

Two independent causes, both fixed:

1. The agent physically could not produce a PNG. It runs as `node`
   under `env -i` with HOME pointed at a per-run scratch dir, so
   `playwright install --with-deps chromium` needs an apt it has no
   rights to and would re-download ~170 MB into a directory deleted at
   the end of every run. Chromium is now installed by ROOT in the tools
   step into a shared, world-readable PLAYWRIGHT_BROWSERS_PATH, and the
   agent env carries that path plus QWEN_VERIFY_CHROMIUM=1 — but ONLY
   when the install actually succeeded, so the variable's absence is a
   real signal rather than a stale promise. The install is best-effort:
   a failure warns and the run continues to a text-only report, which
   is what every run has produced anyway.

2. The skill discouraged it. Captures were "Optionally … use them when
   text cannot carry the oracle", with TUI rendering as the only
   example — so an agent verifying a daemon or a CLI correctly
   concluded text carried the oracle and skipped. It now says produce
   them whenever a harness ran, and says WHY: a table is the agent's
   claim about what happened; a capture of the run is a witness that
   the numbers came from a real execution, which is the part a reviewer
   cannot get any other way. It names the three highest-value shots
   (A/B cells side by side, the mutation matrix as printed, raw harness
   output behind a headline number) and forbids `playwright install`
   explicitly, so the failure mode of cause 1 cannot be rediscovered by
   an agent burning budget on it.

Affordable now because #8014 raised the agent budget to 120m; at 20m a
browser download alone was most of the run.

Mutation-verified 4/4: handing the agent the variable unconditionally,
failing the job on a chromium install failure, reverting the skill to
"Optionally", and dropping the do-not-install warning each turn the
test red.

90/90 tests; prettier, eslint, actionlint and shellcheck clean.

* fix(triage): match browser to lockfile and fix pr-assets branch conflict (#8016)

Split the chromium install into system deps (tools step, as root) and
browser binary (new post-checkout step, as node using the checkout's
own Playwright). This eliminates the version pin that produced an
unusable browser tree.

Change evidence hosting from a bare pr-assets leaf branch — which
cannot coexist with the existing pr-assets/* namespace — to per-PR
branches (pr-assets/pr<N>-verify) with orphan-init fallback.

Update terminal-capture skill to warn CI agents away from the
forbidden playwright install command, and close test blind spots
for conditionality (M1b), version matching (M5), and marker
ordering (M6).

* fix(triage): unpin Playwright install-deps so apt list tracks lockfile browser (#8016)

* fix(triage): assert the browser path reaches the agent, not just the flag

Review finding on #8016, verified by mutation before accepting it:
deleting `"PLAYWRIGHT_BROWSERS_PATH=$CHROMIUM_PATH"` from the agent env
left the test green. That is the nastiest arm of this feature — the
agent is TOLD chromium is available via QWEN_VERIFY_CHROMIUM=1, then
Playwright looks in the default ~/.cache/ms-playwright instead of the
shared install and every capture fails, so a successful install still
degrades to a text-only report.

The test now asserts both variables and that BOTH sit behind the
success guard. Mutation-verified 2/2: deleting the path line fails on
the containment assertion, and hoisting it outside the guard fails on
the ordering one (`expected 21928 to be less than 21834`).

Not changed, because the other two findings do not describe this
codebase — replies on the threads carry the evidence.

90/90 tests; prettier and eslint clean.

* fix(triage): cover orphan-init delivery and delete verify asset branches

Two review findings on #8016, both about the per-PR branch scheme the
autofix bot introduced on this branch (029beba3ef), and both correct.

**Orphan-init's success path had no test.** The existing scenarios both
take the clone-failed branch, but one seeded the branch (so clone
succeeds) and the other points at a non-repo (so the push fails too) —
neither proves orphan-init can DELIVER. A third scenario now points at
a valid bare remote with the branch deleted first: clone fails,
orphan-init runs, and the images land. It also asserts the branch has
exactly one commit, so a graft onto unrelated history would fail rather
than pass as "orphan". Without it, a dropped `git init` or
`remote add origin` would silently discard every image on every PR's
first verification run.

Mutation-verified: dropping `remote add origin` and dropping `git init`
each kill it. A third mutation — pointing `checkout --orphan` at the
wrong branch name — SURVIVES, and correctly: the push is
`HEAD:$assets_branch`, which names its destination explicitly, so the
local branch name cannot affect delivery. Recorded as a non-defect
rather than papered over with a test that pins an irrelevant detail.

**Per-PR asset branches were never deleted.** `pr-assets/*` had exactly
one producer and one deleter; the verify lane became a second producer
without being added, so every verified PR would leave a single-commit
branch in the base repo permanently. The cleanup workflow now deletes
both refs, is renamed to match what it does, and carries a note that
every new `pr-assets/*` producer must be added — a branch nothing
deletes is permanent.

The loop deliberately drops `set -e`: most PRs produce neither branch,
so a 404 on the first must not skip the second. A real delete failure
still warns and sets a non-zero exit. The new test executes the script
against a stubbed `gh` where the visuals branch 404s, and asserts the
verify branch is still deleted — which a `set -e` version would fail.

92/92 tests; prettier and eslint clean.

* fix(triage): match browser install to the harness Playwright and re-arm the strip guard (#8016)

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

* fix(triage): gate chromium marker on deps and resolve Playwright from the harness (#8016)

Address review feedback on the verify evidence-browser path:

- Resolve the Playwright CLI from the capture harness's own directory
  (require.resolve of the exported package.json, then join cli.js) instead
  of assuming npm hoists playwright to the root, so the installed binary
  tracks the harness's import even if the hoist layout changes. cli.js is
  not in the package's exports map, which is why a direct path was used
  before; resolving the exported package.json keeps that bypass while
  removing the hoist assumption.
- Gate the verify-chromium-path marker on a new verify-chromium-deps-ok
  marker written by the tools step. apt and the Playwright CDN are
  independent servers with no shared success signal, so a binary download
  alone must not promise chromium to the agent when system deps failed.
- Add a delete-failure case to the PR asset cleanup test so the status=1
  and exit path is executed, not just statically asserted.

* fix(triage): strip cache creds from browser install and guard cli.js resolution (#8016)

The evidence-browser install runs the PR-resolved Playwright CLI but did
not drop ACTIONS_RUNTIME_TOKEN/URL/CACHE_URL the way the adjacent prepare
and agent steps do, leaving runner-injected cache credentials readable by
PR-controlled code. Strip them in the same env -u chain. Also replace the
string-only guard on the require.resolve + cli.js join with an execution
guard that resolves the expression against the installed tree, so a
Playwright bump that relocates cli.js fails the test instead of silently
degrading evidence capture at runtime.

---------

Co-authored-by: wenshao <wenshao@example.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-07-30 05:37:42 +00:00
Shaojin Wen
cfcc711a70
feat(autofix): back off scan inspection of idle candidates (#8049)
* feat(autofix): back off scan inspection of idle candidates

The scheduled scan inspects every candidate every tick. The takeover
pool doubled in two days (28 open takeover PRs, 8 of them idle in
'nothing new' state for 10+ hours), and idle candidates crowd the two
SHARED budgets: MAX_CANDIDATE_INSPECTIONS (60) and the 10-target cap.
Observed on #8002: freshly engaged, admitted by the 09:03 scan, then
deferred by the target budget while long-idle PRs re-confirmed their
idleness yet again.

Candidates whose list-provided updatedAt (no extra API call) is older
than 24h are now inspected on roughly every 4th scan, on a
deterministic slot keyed by PR number and UTC hour so no PR waits
forever. The skip is free — it sits with the busy skip before the
inspection-budget increment.

Safe by construction: every real wake-up bumps updatedAt (reviews,
comments, labels, pushes) or routes in real time anyway, so the only
thing deferred is the scheduled re-confirmation of idleness plus
worst-case a few hours of base-conflict-detection latency for a PR
nobody touched in a day. The forced-dispatch path never builds the
list files, so a forced PR is always inspected.

* feat(autofix): idle-backoff review follow-ups

- Corrected the comment's cost model: idle candidates hit 'continue'
  before the TARGETS append, so they never contend for the 10-target
  cap — the real win is the shared inspection budget plus the serial
  scan-walk latency (the walk is what delayed #8002's pickup by ~6
  minutes), and the comment now says exactly that.
- Slot quantum changed from the hour to the scan tick (600s, the same
  quantum as ROT_OFF): an hourly slot against the */10 cron meant 6
  back-to-back inspections then a ~3h blind window per PR — same 25%
  average, terrible shape. The gap is now bounded at ~30 minutes, which
  is what the operator-facing strings promise ('gap ≤30m').
- The two scan-only signals updatedAt cannot see (a base conflict
  appearing when main moves; still-red checks awaiting the redcheck
  marker) are named in the comment instead of papered over.
- The per-candidate jq fork became a single precomputed set + a bash
  substring test, matching the busy skip's idiom and the 'free' claim.
- Tests: the skip predicate and set builder got a behavioral replay
  (idle+out-of-slot defers, idle+in-slot inspects, fresh inspects,
  missing-from-lookup inspects); the two byte-distance assertions
  became a loop-head slice (comment growth cannot red-light CI, and
  budget-consuming code between the skips and the increment fails);
  the --json field pin is order-independent; the 3600 quantum is
  pinned OUT.

* test(autofix): pin the null-updatedAt defensive guard in idle-backoff replay (#8049)

* fix(autofix): extract idle-backoff predicate from workflow, fix gap bound 30→40m (#8049)

* test(autofix): pin takeover-prs.json in idle-backoff replay (#8049)

* fix(autofix): state idle-backoff gap probabilistically, unpin quantum (#8049)

Round-2 verification showed the scheduled scan lands every ~40-70 min on
this repo, not every 10 min, so the (epoch/600)%4 slot is an independent
~25% draw per scan, not a deterministic 1-in-4 rotation. The gap is
geometric (measured median ~2h, p90 ~6h), not bounded at ~40m.

Reword the operator-facing strings (skip echo, fleet row) and the workflow
comment to state the behaviour probabilistically and drop the false
"bounded" / "no PR is unlucky forever" claims; correct the misattributed
#8002 latency (queue/startup, not the serial walk). Relax the tests so a
truthful number is not a CI failure: pin the mod-4 time-quantum shape
instead of the exact 600s constant, and drop the /3600 exclusion that
forbade the better-tailed quantum. Mechanism logic is unchanged.

---------

Co-authored-by: verify <verify@local>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
2026-07-30 04:26:43 +00:00
Shaojin Wen
d5ac7a9642
fix(autofix): post the takeover engage ack from the command itself (#8043)
* fix(autofix): post the takeover engage ack from the command itself

The engage ack rode a pull_request:labeled round-trip: takeover-command
applies the label, the labeled event routes, and the takeover-ack job
posts the confirmation. That event has now been observed to simply not
fire twice in one day (#7999 — the author read the silence as failure
and removed the label; #8002 — an engaged fork PR with no ack for
hours), and fork label events can never ack at all since they carry no
secrets: a fork /takeover stayed silent until the next scan picked the
PR up (2h41m on #7993).

takeover-command now posts the engage ack directly after applying the
label — every admission gate has already passed at that point, so
'engaged' is truthful for in-repo and fork PRs alike; the fork variant
adds the expectation that the first round comes from the next scheduled
scan. The route side suppresses the label-path ack when the label
sender is the bot (only the ack: the immediate scan still routes), and
the review-scan's existing first-pickup ack dedups against the
command's comment and heals it if the post failed.

Two more silent paths become audible while here: a /takeover on a
stacked (non-main-base) PR now refuses out loud instead of dropping
with only a log line, and a /takeover stop on a non-main PR now
proceeds to remove the label instead of leaving it stuck.

* fix(autofix): ack command-driven releases directly and key the scan grace on the label actor

Review follow-up: the engage-side fix left the release side on the
fragile round-trip — a loud add next to a mute stop re-creates the
exact 'did it work or did the event get lost?' ambiguity this PR set
out to remove, now on release. And a fork or non-main release could
never ack at all (fork unlabeled events carry no secrets; the route
ignores non-main releases).

- takeover-command now posts the release ack directly after removing
  the label, mirroring the engage side, with the same three variants as
  the ack job (plain release / bot-authored / bot-authored+skip) chosen
  from the same PR_INFO the gates used. The route suppresses the
  unlabeled-path ack when the label sender is the bot.
- The scan's first-pickup grace is keyed on the label event's actor:
  a bot-applied label defers only 45s (the command's own write is
  seconds behind the label — fork or in-repo alike), so the immediate
  scan still heals a failed command ack instead of slipping to the next
  scheduled tick, and an ic.json snapshot taken between the label write
  and the command ack cannot double-post. A human in-repo label keeps
  the 3-minute ack-job grace; a human fork still posts right away.
- The fork note now says 'usually within minutes', matching the
  */10 cron instead of contradicting it by 6x.
- Tests kill the two surviving mutations from review: the EN/ZH
  fork-note swap (per-language-half assertions) and the warning
  fallback downgraded to '|| true' (both fallback strings pinned).

* test(autofix): behaviorally pin the LAST_LABELED_BY jq extraction (#8043)

* fix(autofix): correct stale comments about scan heal timing and command blast radius (#8043)

* test(autofix): pin release-ack body identity across the command and ack jobs (#8043)

---------

Co-authored-by: verify <verify@local>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
2026-07-30 02:41:08 +00:00
Shaojin Wen
c631c0448e
fix(autofix): cumulative timeout breaker, narrowed retry prompt, truthful handoff wording (#8044)
* fix(autofix): stop repeat-timeout PRs with a cumulative breaker and narrow the retry prompt

The consecutive-failure cap resets on every pushed round, so it never
fires on the observed failure shape: timeouts interleaved with
successes. #7929 burned three 50-minute agent timeouts (with pushed
rounds in between) and #7846 two — each a full agent budget with
nothing to show, invisible to the breaker by construction.

Three changes:

- Cumulative timeout breaker (TIMEOUT_WINDOW_CAP=3): time-budget
  exhaustions are counted over the whole counting window, successes in
  between included. At the cap the PR goes terminal with a
  skip-until-/retry headline, exactly like the consecutive breaker. The
  census reuses PRIOR_HEADS, so it is window-scoped and a re-arm
  clears it.

- Prompt narrowing from the second attempt: when the current window
  already contains a timeout marker, the prepare step appends a budget
  warning to feedback.md — smallest blocking subset first, commit
  early, defer the rest explicitly — instead of re-running the
  identical address-everything prompt into the same wall.

- Truthful gate-rejection handoff: 'A human should take over this PR'
  read as a full release, but the loop keeps managing the PR (new
  feedback, base conflicts) — #7929 posted it and then kept pushing
  rounds. The headline now says exactly which half is over: this item
  will not be retried automatically, management continues.

* fix(autofix): timeout-breaker review follow-ups

All six review findings addressed:

- The narrowing prompt now restates the SKILL deferral contract instead
  of pointing at 'your summary': deferred findings stay out of
  resolved-comments.txt and get a comment-replies.json entry so every
  open thread carries its reason — the summary-only shortcut is exactly
  what a budget-pressured agent would otherwise take. 'skip refactors
  entirely' became 'decline with a one-line reason' for the same
  never-drop-silently rule.
- The narrowing census counts timeouts SINCE THE LAST SUCCESSFUL ROUND,
  not cumulatively: a push falsifies 'not converging', and one old
  timeout must not degrade every later round of a 100-round window. The
  breaker stays cumulative (a push does not make the next timeout
  cheaper in budget terms).
- The breaker headline states what the census measured ('this counting
  window now contains N time-budget exhaustions') instead of 'stopped
  after N' — the tripping round can itself be a gate rejection, which
  is the exact rollout state of #7929/#7846.
- Both census needles anchor on the verbatim emitted headline ('AutoFix
  ran out of time before finishing') — first lines can embed provider
  error text, so the loose phrase could count a model error as a
  timeout.
- The precedence assertion got teeth: 'consecutive' alone matched both
  branches; it now asserts the consecutive breaker's own phrase and the
  absence of the timeout one (the if-true guard mutation now fails).
- The narrowing census got a behavioral replay over fixture ic.json:
  push-resets, trailing counts, legacy no-win markers under 'none',
  old-window exclusion, author filtering — plus a pin on the -ge 1
  trigger so a threshold mutation cannot leave the feature inert.

* fix(autofix): state what the timeout breaker measured, pin the inherited guarantees

Second-round review follow-ups:

- The breaker headline no longer infers 'is not converging' — a window
  can carry 40 pushes and still trip the (deliberately cumulative) cap,
  so the sentence now states only what the census measured: N full
  agent runs that pushed nothing.
- Two guardrail tests pin behavior that held only by construction: a
  non-timeout failure landing on an already-capped window still trips
  the breaker (the documented rollout state — an 'only count when this
  round timed out' cleanup would silently delete it), and the
  transient-API-error exemption inherited from the outer guard (a
  refactor hoisting the block out of it must not mass-terminate every
  in-flight PR during a provider outage).

* fix(autofix): name a gate rejection only when the gate ran; pin the timeout breaker's stale-base exemption

Third-round review follow-ups:

- The handoff headline no longer claims 'the verification gate rejected
  the attempt' for every outcome=failed verdict: that branch is reached
  for five distinct failures (failure.md abort, dirty tree, unchanged
  branch, missing summary, and a real gate rejection), only one of which
  is a gate decision. The clause is now gated on gate-rejection.md,
  which reject_fix is the sole writer of — so the other four paths keep
  the neutral framing instead of repeating the very
  wording-doesn't-match-behaviour bug this PR fixes. A replay test pins
  both halves: the clause appears when gate-rejection.md exists and is
  absent when it does not.
- The timeout breaker's stale-base exemption is now pinned by a replay
  (five in-window timeouts plus a stale-base retry current round stays
  retryable), matching the existing transient-API-error pin: a refactor
  hoisting the timeout block out of the outer guard would otherwise
  delete this exemption silently.
- The prepare-step narrowing census jq now fails open (2> /dev/null ||
  true) like its report-step sibling, so a malformed ic.json drops the
  budget warning instead of aborting prepare under errexit and turning
  the round into a terminal 'could not start evaluation' handoff.
- The decay comment now matches the code: the warning resets on a
  push/no-op round but fires on every failing round until then (gate
  rejections included), which is correct since nothing has converged.

* fix(autofix): clear stale gate-rejection.md in repair cleanup and pin headline template (#8044)

* test(autofix): cover no-op reset branch in timeout census (#8044)

---------

Co-authored-by: verify <verify@local>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-07-30 02:41:00 +00:00
Shaojin Wen
25f5147677
feat(autofix): post a takeover milestone digest every tenth pushed round (#8046)
* feat(autofix): post a takeover milestone digest every tenth pushed round

The takeover round cap (100) bounds runaway but carries no signal about
when a human should step in: #7469 ground to round 12 over seven days
of takeover with fifteen pushes, and the only place that trajectory was
visible was the Actions logs.

Every 10th pushed round under takeover, the report step now posts a
window-scoped census on the PR itself — pushed fixes, no-change
reviews, timeouts, rejected attempts, base updates — plus the three
options (keep going / split or reduce / release takeover). The digest
is a separate comment with its own autofix-milestone marker and no
autofix-eval marker, so every census (round, consecutive-failure,
watermark) ignores it and the feedback filters keep it out of the
agent's prompt. Posting is best-effort: a digest failure never fails a
good push.

The rejected-attempt count matches both the current and the reworded
gate-rejection headline so the census cannot silently zero; base
updates carry no win= field and are windowed by timestamp instead (the
window key is the engage ack's created_at).

* feat(autofix): milestone digest review follow-ups

- Residual bucket: every outcome the four buckets missed (crash, model
  error, gate error, infra) now lands in 'other round(s)' — a window
  that burned 80% of its budget on crashes was rendering as four zeros,
  QUIETER than a healthy window, the inverse of the digest's premise.
- Crossing trigger: fire on the first pushed round once 10+ rounds have
  accumulated since the last digest in this window, instead of an exact
  %10 hit — failure rounds advance the counter too, so push@9/crash@10/
  push@11 skipped the digest forever on exactly the failure-heavy PRs
  it exists for.
- The success log is chained to the post; a failed comment no longer
  logs 'posted' after its own warning.
- WINDOW=none says 'since the PR opened (no counting window yet)'
  instead of claiming a current window while counting all time.
- A census that parses zero window markers at round 10+ skips the
  digest instead of posting a fabricated all-zero one.
- autofix-milestone joins BOT_COMMENT_FILTER (marker inventory).
- The timeout needle anchors on the verbatim emitted headline.
- The pin-only test became a behavioral replay: the digest block runs
  under bash with a stubbed gh over fixture ic.json histories — bucket
  counts, residual loudness, crossing suppression/resumption, old-window
  isolation, none-window phrasing, non-pushed/non-takeover gating, and
  the empty-census skip. The OUTCOME == fixed conjunct is pinned.

* test(autofix): cross-pin digest census needles to headline emission sites (#8046)

* test(autofix): widen rejected-headline cross-pin to match #8044 reword (#8046)

* test(autofix): behaviorally cover the digest comment-failure branch (#8046)

---------

Co-authored-by: verify <verify@local>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-07-30 02:40:55 +00:00
Shaojin Wen
36fe53d421
feat(triage): raise the /verify agent budget from 25m to 120m (#8014)
* feat(triage): raise the /verify agent budget from 25m to 120m

Maintainer decision: give /verify enough time to do what a maintainer's
local round does, and add machines if the pool gets tight.

Three values encode this one budget and each has its own silent failure
when edited apart, so all three move together:

- agent kill      25m  -> 120m   (the graceful budget; ships a partial
                                  report on expiry)
- watchdog secs   1500 -> 7200   (distinguishes that kill from an OOM;
                                  left low, a late 137 is mislabelled
                                  `timeout` and publishes "partial
                                  evidence" for a crash)
- job timeout     60m  -> 150m   (guards infra hangs only; it must clear
                                  agent + install/build + overhead or it
                                  kills the container mid-run and the
                                  ship-what-ran path never executes)

150 rather than 130: measured install+build is ~6m (run 30284341325,
npm ci 3m00 + build 2m40), budgeted at 15m for a cold cache, plus ~5m of
tools/checkout/pin/upload/cleanup — worst case ~140m, leaving 10m of
headroom.

The skill's advertised budget moves too, and this is the part that
actually changes behaviour: it read "≈ 20 minutes (hard 25-minute
kill)", and an agent obeying that would have self-limited no matter what
the workflow allowed. It now reads 110/120 and says what the extra time
is FOR — bisecting a threshold through the real code path, compiling an
intermediate build to separate the halves of a bundled fix, adjudicating
mutation survivors, driving a real daemon end to end. It also says
plainly that spending it on breadth is the one way to waste it; the
rule that one proven load-bearing claim beats ten unverified
observations does not relax because the clock did.

Pinned by a new test that asserts the RELATIONSHIPS rather than the
numbers: watchdog == agent budget in seconds, job >= agent + 20m, and
the skill's advertised hard kill == the workflow's, with the soft budget
strictly below it. Mutation-verified 4/4 — leaving the watchdog at 1500,
the job at 60, the skill at 25m, or raising the soft budget above the
hard kill each turn it red.

Cost, stated so it is a decision and not a surprise: a verify run now
holds one ECS slot for up to 2.5h instead of 1h. Concurrency is
unchanged (per-PR group, cancel-in-progress false), so this reduces how
many distinct PRs can verify at once, not how many runs a PR can queue.

90/90 tests; prettier, eslint and actionlint clean.

* fix(ci): pin soft-budget lower bound in verify consistency test (#8014)

* fix(ci): harden verify budget guard and clarify budget is a ceiling (#8014)

* fix(ci): measure elapsed before artifact copy and tighten budget guard (#8014)

* test(ci): pin elapsed-time chain in verify budget drift guard (#8014)

* refactor(ci): derive verify watchdog threshold from the agent budget (#8014)

The watchdog threshold that tells a graceful timeout (137 at the budget)
from an OOM kill (137 before it) was a bare `7200` coupled to the `120m`
agent timeout only by a comment. Define the budget once as AGENT_BUDGET_M
and derive both the timeout (`${AGENT_BUDGET_M}m`) and the threshold
(`$((AGENT_BUDGET_M * 60))`) from it, so editing one cannot silently
desync the other. The consistency test now asserts that derivation rather
than re-extracting two independent literals.

Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>

---------

Co-authored-by: wenshao <wenshao@example.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
2026-07-30 02:40:34 +00:00
Shaojin Wen
0232e73811
feat(verify-pr): add seven techniques from maintainer verification rounds (#8010)
* feat(verify-pr): add four techniques from maintainer verification rounds

Two hand-written maintainer rounds contained methods the skill could not
have produced. Checked each against the current text before adding it;
these four had no coverage at all.

From #7914 (live daemon A/B on the artifact-recording change):

- Run every control on BOTH arms. That round's sharpest finding came
  from a control whose only job was to validate the BASE probe — "the
  empty list is a real absence, so have the model call record_artifact
  and watch an entry appear". Run on head as well, it showed the
  curated title being silently discarded. The control was not hunting
  for a bug; running it symmetrically is what found one.

- A new writer into a shared store is an ordering change. The PR added
  write_file as a second writer into the artifact list; the bug was not
  in the new writer but in the collision, where a pre-existing
  first-writer-wins merge began discarding record_artifact's curated
  title and description while still reporting success. Enumerate the
  other writers, exercise the collision in both orders, and check what
  the loser is told — and separate the pre-existing cause from the PR's
  contribution so the author is not blamed for the policy.

From #7998 (ink cursor fix, real-terminal A/B):

- When the oracle is an instrument, corroborate it with a mechanism
  that does not use that instrument. The hardware cursor row came from
  `tmux display-message -p '#{cursor_y}'`, then from a marker printed
  after the TUI exits — which lands wherever the cursor actually was.
  Two agreeing instruments turn a measurement into evidence; one tool's
  report about the system is not the system.

- Re-run the generator on committed generated artifacts and diff. That
  round re-ran `npx patch-package ink` and found byte-different hunk
  headers, proving the .d.ts hunks were hand-written rather than
  regenerated as the description claimed.

Also strengthens Not covered: proving a limitation is environmental
requires an A/A control (boot base and head identically, show both fail
the same way), because "seems environmental" and a real regression look
identical in a report.

Mutation-verified 4/4, each with landing proof. Two initially reported
`landed: False` — the assertions match the whitespace-normalised text
while the rules wrap across lines in the source, so the replace never
fired and the green result proved nothing. Re-run against line-accurate
anchors, both kill.

89/89 tests; prettier and eslint clean.

* feat(verify-pr): teach the timing-race and scenario-arrival checks

Third maintainer round mined for method (#7934 R4). The blocker it found
had zero coverage in the skill — `timer`, `wall-clock`, `flake`,
`retry`, `duration`, `deterministic` all returned 0, and the one `race`
hit was a substring of "trace".

- **Timing-triggered assertions have a threshold — measure it, do not
  sample it.** A new guard (`expect(false).toBe(true)` after an abort
  loop) turned a vacuous pass into a deterministic failure, because the
  case triggers its abort from `setTimeout(..., 1000)` while the query's
  duration is set by CLI startup rather than the server. Natural
  completion measured 730-2151 ms, so every box on the fast side of
  1000 ms fails. The rule says to measure the operation's natural
  duration with the trigger disabled and compare it to the timer,
  because a green run only proves this box was slow enough.

- **A speed-correlated failure is not flake, and a retry budget does not
  absorb it.** Random flake becomes a pass under `retry: 2`; this failed
  5/5 runs on all three attempts. The two get opposite verdicts, so the
  kind has to be established before the verdict is written.

  Stated plainly in the skill: the verify job runs on a shared, loaded
  runner — the regime where such a test PASSES. Repetition cannot
  reproduce a fast-machine failure there; only computing the margin can.
  A rule that said "run it more times" would be useless in this lane.

- **The failure one level before vacuity: the scenario never reached the
  code under test.** The vacuity check asks whether an assertion can
  fail; this asks whether the code ever ran. Four abort cases fired
  during CLI process startup, so the fake server saw zero requests and
  a suite named for mid-stream aborts never streamed — with every
  assertion green. Instrument the seam and assert the count is
  non-zero.

Mutation-verified 5/5, each with landing proof against line-accurate
anchors.

89/89 tests; prettier and eslint clean. Skill is 472 lines, up from 392.

* feat(verify-pr): six more techniques, from three maintainer rounds

Mined #7836 R2, #7885 and #7899 for method. Checked each candidate
against the current text first; six had zero coverage, the rest were
already there (harness teeth-checks, pin dereferencing, boundary
probing, and the follow-up round's "re-measure, never diff").

The one that corrects the skill's own core method, from #7836:

- **Before calling a survivor vacuous, escalate to a finer mutation.**
  A whole-file revert is blunt enough to remove the PRECONDITION a test
  depends on, so a good test goes green because its scenario no longer
  occurs — from the outside, identical to a test that asserts nothing.
  A `finally`-cleanup test survived reverting four production files and
  died to deleting one line. Coarse survived + fine killed ⇒ the test
  is fine and the mutation was wrong. A false "your test is vacuous"
  costs the author more than a missed survivor does.

From #7836, the root cause shared by both of its blockers:

- **When the same predicate is checked in two places, verify they see
  the same state.** A guard duplicated across a process boundary is two
  implementations of one question that diverge when their INPUTS differ.
  One settings key made a route ask sessionExistsInAnyState() with an
  unpinned runtime dir while the child asked with a pinned one, turning
  a clean 409 into a 500 plus a process.exit(1) that killed every
  session on the channel. Includes the temporal half: lazily-created
  backing files leave a window where a just-created entity is invisible
  to any on-disk existence check.

- **Measure the blast radius on bystanders.** The caller's own error
  code understates a shared-state failure; the number that matters is
  an unrelated session going 200 -> 404 and a workspace list going
  2 -> 0.

From #7885, which took a performance claim apart:

- **Isolate the slice the mechanism can actually affect.** A speedup
  claim is two claims: the mechanism works, and the thing it speeds up
  matters. `--ignore-scripts` isolated what an npm download cache can
  touch — 36s of a 226s install — so the ceiling was 20s and the real
  saving 15%, not the claimed 75%. Then check it against the whole job:
  33s off 14m37s.

- **A mechanism that persists something has a cost — price it.** 219 MB
  per lockfile hash into a pool at 9.98 GB of a 10 GB cap, with 39
  distinct lockfile states in 30 days: at the cap every entry evicts by
  LRU, including entries other jobs need and its own.

- **Test the scarier consequences and report which do NOT hold.** The
  write-path finding was real; code injection was disproved (npm
  integrity-checks a tampered cache and refetches) and privilege
  escalation was disproved (chown -R does not follow symlinks). A
  finding that names what it is not is harder to wave away.

- **Verify third-party actions from their own manifest.** The PR said
  the cache dir was discarded after the job; `action.yml` declares
  `post: dist/save/index.js` with `post-if: success()`, which uploads
  it as root with credentials intact — the opposite of the claim, and
  the whole finding.

From #7899:

- **To exercise real production data safely, interpose a refusing proxy
  on the write path.** Wrap the client so every mutating call hard-
  fails, then run the shipped script verbatim: real counts, mechanical
  guarantee of no side effects.

Mutation-verified 9/9, each with landing proof against line-accurate
anchors. 89/89 tests; prettier and eslint clean. The skill is now 546
lines, up from 392 — the growth is deliberate and called out in the PR
body's risk section.

* feat(verify-pr): decomposed fixes, contextual limits, destination counts

From #7862 R4. Three additions, and a deliberate stop.

- **When one fix bundles two changes, build the intermediate variants.**
  An A/B against base proves the pair works and says nothing about what
  each half does. That round compiled a third build with only the
  ordering change reverted, and the three-row table showed the halves
  do different jobs: moving `initialized = true` after the fallible work
  converts a 2,999-and-climbing backlog flood into a fail-safe retry,
  while `reduce()` restores liveness. Either alone leaves a channel that
  floods or wedges — a conclusion the two-cell A/B cannot reach.

- **A limit measured in isolation does not transfer to the real call
  site.** The same `Math.max` spread threw between 110k and 130k
  elements inside a deep async stack, well below a standalone
  micro-benchmark. Bisect thresholds through the real code path and
  quote the harness; a limit taken from documentation or a toy loop is a
  guess about the system under test.

- **Count at the destination, not at the component boundary.** The
  mirror of the scenario-arrival rule added earlier: envelopes the
  adapter emitted and prompts that reached the agent are different
  numbers, and every gate lives between them. A count taken at the seam
  can be right while the feature is silently dropped downstream.

Declined from the same report, to protect prompt budget rather than
because they are wrong: siblings-as-convention-oracle (the lockfile
version table across five channels), degenerate fixtures that cannot
distinguish two sort keys, and naming the condition under which a
cosmetic finding becomes real. Each is a good technique; none is worth
another rule competing for attention with the ones already here.

The skill is now 578 lines, up from 392 on main (+47%) across this
branch. That growth is the main risk on this PR and further additions
should wait until a live round shows the current set changes behaviour.

Mutation-verified 3/3 with landing proof. One mutation initially
SURVIVED — it deleted text sitting AFTER the asserted phrase, so the
assertion still matched and the green proved nothing; re-run against
the phrase itself, it kills.

89/89 tests; prettier and eslint clean.

* test(scripts): drop stale technique count from verify test name (#8010)

* fix(triage): correct verify-skill worked examples and verdict path (#8010)

Address review feedback on the verification-techniques skill:

- Make the npm-cache worked example's numbers close: separate the 20 s
  download-slice ceiling (36 s to 16 s) from the 15% end-to-end saving
  (226 s to 193 s) rather than conflating them.
- Stop overstating the tarball experiment: one tarball was poisoned, and
  the 2262-entry integrity coverage is a separate static fact.
- Give the speed-correlated-failure rule a contract-legal verdict path by
  encoding the margin as a scripted assertion, and mark the load/idle
  sweep as the local-mode variant.
- Fix the one bullet that broke its 2-space list continuation.
- Pin the new contract-encoding clause in the workflow test.

---------

Co-authored-by: wenshao <wenshao@example.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-07-30 02:40:26 +00:00