Commit graph

651 commits

Author SHA1 Message Date
qwen-code-dev-bot
6f73fbf742 fix(ci): keep pull requests off the persistent Windows pool and cover native audio (#9370)
A pull_request run executes the workflow YAML from the PR's own merge
commit, so the test_windows runs-on trust clause it evaluated could be
rewritten by any PR the lane admits. Every pull request now runs on
hosted windows-2022 unconditionally; the pool is reached only by the
post-approval merge queue, schedule and dispatch, guarded by the
kill-switch. The routing tests and the exact-line pin are re-pointed at
that enforceable shape.

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

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-22 05:07:01 +00:00
Shaojin Wen
07040ac28e
Merge branch 'main' into fix/revive-platform-lanes 2026-08-22 09:34:44 +08:00
Shaojin Wen
83f2589273
fix(autofix): include pending runs in the busy-PR enumeration (#9662)
* fix(autofix): include pending runs in the busy-PR enumeration

The scan's busy enumeration lists live autofix runs server-side by
status, but unions only in_progress and queued. GitHub reports a run
'pending' while its remaining jobs wait on concurrency groups, and the
run-level status trails the job-level flip by minutes — so legs that
were already running stayed invisible to the skip. Measured 2026-08-21
(#9596): one scan re-dispatched four PRs whose legs ran while their run
still listed 'pending'; every duplicate burned one build-cli before
queueing behind the per-PR group it should have skipped, and the
duplicate queued behind #9596's running leg held its dispatch-pending
status open for close to an hour.

Add pending to the union. Pending runs cost one extra jobs-view each
and match nothing until their matrix materialises; the fail-closed
rule and the dispatch-pending marker check are unchanged. The af-026
design record and the test pinning the status union move with it.

* fix(autofix): enumerate busy runs via the runs API, not gh run list --status

* fix(autofix): read the runs-API envelope's id, not the gh-CLI databaseId

The runs-API rewrite of the busy enumeration filtered with
.workflow_runs[].databaseId, but the REST payload has no databaseId
field — that name only exists in the gh CLI's JSON projection. Against
the real API the filter prints one empty line per run, the enumeration
loop skips empty lines, and every scan silently keeps an empty busy
set: busy detection becomes a no-op on every runner, with no
fail-closed trigger ever firing. Verified live: the payload's keys
carry `id`, and `.workflow_runs[].id` returns real run ids for
in_progress, queued, and pending alike.

Fix the filter, pin the field name positively and negatively, and
align the behavioral-replay fixtures with the real REST envelope so
the harness can only pass against the shape the API actually returns.

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-08-21 15:33:47 +00:00
qwen-code-dev-bot
bab6596c1a fix(ci): gate the two mapfile-crossing gate tests on the host probe (#9370) 2026-08-21 09:58:56 +00:00
易良
e40263ee55
chore(deps): Clear high-severity CVE baseline and harden the security gate (#9584)
* chore(deps): Clear high-severity CVE baseline and harden the security gate

- Bump OpenTelemetry stack to 0.221.x (fixes @opentelemetry/core advisories)
- Bump @larksuiteoapi/node-sdk to ^1.73.0 and override axios to ^1.19.0
- Bump mobilewright to ^0.0.53 (drops vulnerable sharp 0.34.x)
- Bump markdown-it to ^15.0.0 (drops vulnerable linkify-it 5.x)
- Update undici/fast-uri/brace-expansion/ip-address within range
- Adapt telemetry code to OTel API changes (forceFlush, processor options)
- Make security-checks a hard gate now that the high baseline is clean

* chore(deps): Refresh mobile-mcp vendored lockfile to drop vulnerable sharp

* fix(telemetry): stub sdk-node 0.221 env auto-config helper packages

sdk-node 0.221 extracted its env-based auto-configuration into
@opentelemetry/configuration, otlp-exporter-base, and
otlp-grpc-exporter-base, which it now requires eagerly. The existing
esbuild stub only covered the exporter-* packages, so the OTLP protocol
chain (grpc-js, protobufjs, otlp-transformer) re-entered the sdk-impl
static closure and tripped the serve fast-path bundle guard.

Stub the three helper packages when imported by sdk-node only; our own
protocol modules keep resolving the real packages. qwen-code never
reaches these helpers at runtime (explicit exporters + env scrub).

* fix(telemetry): disable metrics fallback without reader

* fix(vscode): restore nested dependency notices

* fix(deps): declare bundled punycode so its notice survives regeneration

The CLI esbuild config aliases punycode to the userland package
(esbuild.config.js), so the shipped CLI bundle contains MIT-licensed
punycode@2.3.1. Its NOTICES.txt section was lost because the only
lockfile paths reaching punycode were dev-only; the notice walker
(rooted at vscode-ide-companion) never sees a production declaration.

Declare punycode as a direct production dependency of the CLI (the
bundle input) and of vscode-ide-companion (which packages the bundled
CLI into the VSIX and owns NOTICES.txt), then regenerate the lockfile
and notices so the MIT notice is restored.
2026-08-21 07:43:32 +00:00
Shaojin Wen
575e62ee46
fix(autofix): bind the sandbox image to its pulled digest (#9527)
* fix(autofix): bind the sandbox image to its pulled digest

The sandbox image was exported as a mutable tag. `docker run <tag>`
resolves against the local store without re-pulling, so a co-resident
process with daemon access can `docker tag` different content under the
same name between the resolve step and the consumer. Export the
`<repo>@sha256:...` RepoDigests entry that matches both the pulled
repository and the digest the pull itself reported: RepoDigests is shared
by every tag of the same content, so index 0 can move off the pulled repo
under a same-content retag, and retagged foreign content keeps its own
repo — only the pair binds the export to what the pull fetched.

Pin the daemon endpoint for both spawns. The docker CLI resolves its
endpoint from DOCKER_HOST, then --context, then DOCKER_CONTEXT, then
`currentContext` in the pool-shared config.json; clearing DOCKER_CONTEXT
falls through to that last one, so the context is named explicitly and
DOCKER_HOST is dropped from the child environment. An inspect answered by
someone else's daemon hands back any digest it likes.

Write the step files through a non-blocking, type-checked append.
$GITHUB_ENV and $GITHUB_OUTPUT live under the runner-writable temp tree,
where a planted FIFO turns a plain append into a block until the step
timeout.

Extracted from #9214, which is frozen; these were R11-1 and R11-2 there.
The inspect timeout is now injectable so the tests can pin it, and the
suite covers the endpoint pin on both spawns, the FIFO and directory
refusals, cross-chunk stdout accumulation, and the timeout itself. Each
new test was checked against a mutant of the code it pins.

Refs #9089, #9524.

* fix(autofix): bind gate image inputs to the resolver step output (#9527)

* fix(autofix): revert repo-hygiene binding outside PR footprint (#9527)

The deterministic gate rejected the previous commit because
repo-hygiene.yml is CI machinery this PR never touched; review
feedback alone cannot authorize changes there. Restore the file
byte-for-byte and scope the workflow contract test to the two
autofix workflows this PR binds. The repo-hygiene binding is real
and is deferred to the review-findings follow-up queue for a
maintainer-owned change.

* fix(autofix): harden sandbox image consumers per review round (#9527)

- R1-2: extract the duplicated spawn guard (endpoint pin, settle-once
  finish, SIGKILL timer, stdout capture, error/close wiring) into one
  spawnDockerCapture helper; pullImage and repoDigestOf share it.
- R2-1: contract test fails when a workflow detects zero sandbox
  consumers instead of passing vacuously.
- R2-2: success-path e2e test for the digest-bound export; verified it
  kills the exportImage(image) mutant.
- R2-3: pin the daemon endpoint (DOCKER_HOST: '', DOCKER_CONTEXT:
  default) on every sandbox-consuming step, closing the $GITHUB_ENV and
  pool-shared currentContext channels past the resolver; contract test
  enforces the pin.
- R2-4: gate the repair step on the resolver outcome so a failed
  resolver can never relaunch the agent unsandboxed.

Also updates the workflow source pin in scripts/tests to the shared
helper's literals (required by the R1-2 refactor).

* test(autofix): pin repair outcome gate, derive contract set (#9527)

- R3-1: the contract test now requires every always()-gated consumer to
  also gate on the resolver step outcome, pinning the R2-4 fail-closed
  clause; verified that deleting the guard from the repair step now
  fails the suite (the mutant shipped green before).
- R3-2: route both main() e2e tests through withDockerStub; the refusal
  test's untouched-file asserts move before the temp-dir cleanup — they
  previously ran after rmSync, so they passed no matter what the
  resolver wrote.
- R3-3: derive the contract test's protected workflow set from the tree
  instead of a hand-enumerated list, so a new resolver step cannot land
  untested; repo-hygiene.yml stays in an explicit, staleness-checked
  exception set until its deferred binding lands.

* fix(autofix): pin resolver binary, make image check digest-aware (#9527)

* test(autofix): share resolver e2e scaffold, tripwire stale exemptions (#9527)

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-08-21 06:46:52 +00:00
qwen-code-dev-bot
bcaa9a3c33 Merge remote-tracking branch 'origin/main' into fix/revive-platform-lanes
# Conflicts:
#	scripts/tests/qwen-autofix-workflow.test.js
2026-08-21 05:37:44 +00:00
Shaojin Wen
2c64ebe980
feat(autofix): audit the approach instead of stopping on growth-budget breach (#9262)
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
Security Checks / Dependency CVE audit (push) Waiting to run
Security Checks / Secret scan (TruffleHog) (push) Waiting to run
* feat(autofix): audit the approach instead of stopping on growth-budget breach

A growth-budget breach no longer escalates to a maintainer handoff that
stops the takeover. The breach now makes the round a growth-audit round:
the agent audits the PR's approach on two axes — KISS (name a simpler
alternative or prove each piece load-bearing) and minimal change (every
hunk traces to the problem, an accepted finding, or a failing check) —
and records a machine-readable verdict that the verification gate
requires. sound re-arms the counting window at the current size and the
loop keeps solving; drift simplifies first, then continues; conflict is
the only growth path to a human, parked idempotently until a trusted
human responds.

The old divergence ladder (over budget for N rounds and not shrinking →
stop) terminated takeovers whose remaining work could still fit: the
growth it punished was protocol-mandated pinned tests (#9213 stalled at
round 5 with two small Criticals left). A size signal now triggers a
judgment, never a stop.

Design: docs/design/autofix-growth-audit.md

* fix(autofix): update the artifact-list pin for the growth-audit.json upload entry

* fix(autofix): surface conflict verdicts past the failure.md exits and strip verdict forgery channels (#9262)

* fix(autofix): harden the growth-audit verdict pipeline and park wake set (#9262)

* fix(autofix): close the verdict-pipeline forgeries and loop-generated wake entrances (#9262)

* fix(ci): drop the retired divergence rationale records (af-046/af-047) from qwen-autofix.md

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-21 04:54:07 +00:00
易良
a074d3b042
chore(ci): Disable install scripts in release CI and guard security-checks workflow (#9577)
* chore(ci): Disable install scripts in release CI and guard security-checks workflow

* fix(ci): complete release install hardening

* test(ci): pin release install step count

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

* fix(ci): scope release PAT to push step

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

* fix(ci): export GH_TOKEN so the release-branch push uses CI_BOT_PAT

* fix(ci): export GH_TOKEN so the credential helper sees it at push time

An inline GH_TOKEN prefix only covers the gh auth setup-git call itself;
the helper re-resolves the token when git push invokes it, so the push
would fall back to the job token with persist-credentials disabled.

* fix(test): anchor setup-git ordering check after the export line

A comment in the push step mentions gh auth setup-git before the export,
so indexOf found the comment first and the ordering assertion inverted.

* style(test): wrap long line to satisfy prettier

* fix(ci): address review findings on PAT handling and install comments

- Pin gh auth setup-git before the git push it authenticates in both
  release and finalize workflow tests, so moving credential setup after
  the push no longer passes.
- Correct the replay comment: npm run generate is not a lifecycle
  script and workspace lifecycle scripts stay disabled.
- Drop the overstated push-boundary claim and record why the push
  needs the bot PAT rather than the job token.

* test(ci): pin CI_BOT_PAT out of install steps and the publish job header

* style(test): apply prettier's exact re-wrap for the two flagged calls

* test(ci): pin CI_BOT_PAT out of the workflow-level headers too

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-21 03:48:03 +00:00
Shaojin Wen
9f2342d323
fix(ci): stop the fallback comment from denying a review it already posted (#9462)
* fix(ci): stop the fallback comment from denying a review it already posted

The review job can fail AFTER posting its review — the CLI exiting
silently, a cleanup step dying — and both fallback sites then announce
that review as one that could not be posted, retry instruction attached.
Measured on PR #9342: the review posted at 11:56:34Z, review-pr failed
at 12:00:53Z ("Qwen review completed but produced no output"), and the
comment landed at 12:01:00Z saying the pipeline "failed before a review
could be posted. … retry with @qwen-code /review" — a fresh ~3-hour
review, asked for beside the review that had just landed. The autofix
takeover loop reads the same feed a human does.

Both sites now check, before composing a body, whether a review this run
posted is already on the PR. The check is scoped three ways so a stale
review can never buy silence on a genuinely dead pipeline: the bot's own
account, the head this run reviewed, and a submission at or after this
run started. Where the proof is unavailable — no start time, no head, a
failed listing — the guard declines to fire and the comment posts, the
same call the head-moved guard already makes.

The job-level step now reads state and headRefOid in one `gh pr view`
(the in-job step already did), which is where its head value comes from.

Tests run the steps' real bash over review fixtures, because the guard
IS a filter: silence when this run posted the review, and posting for
each near-miss on its own — an earlier run's review at the same head,
another account's, one of a different head, a PENDING one, none at all,
an unavailable start time, and a failed reviews listing. One existing
assertion tightened: "no `gh run view`" was the proxy for "no head
comparison on comment runs", and the new guard asks that same command
for startedAt on every event, so it now pins the head lookups
themselves. The stub's state,headRefOid branch learned the pr_closed
scenario its state-only sibling already knew.

* fix(ci): anchor the already-posted guard on the run's creation, and say when it cannot run

Round 1's two blockers, both re-verified against this repo's own run data.

The time anchor reset on job re-runs. `gh run view --json startedAt`
returns the LATEST attempt's start while the run id stays the same — the
dedup above relies on that stability — so a re-run pushed attempt 1's
review outside "this run": runs 32219268680 (created 05:23:57Z,
startedAt 05:51:26Z) and 32218596441 (05:13:04Z → 05:22:05Z) both show
the ~9-28 minute shift. Attempt 1 posts its review, the job fails after
the post, someone re-runs it, attempt 2 fails before posting — and the
guard, anchored on attempt 2's start, lets the contradictory comment
through. Exactly the shape this PR exists to stop, on the path most
likely to reach it. Both sites now anchor on `createdAt`, which is
attempt-stable; a review submitted after the run was created still
cannot belong to an earlier run, so the stale-review protection is
unchanged.

The guard also swallowed its own lookup failures. A transient failure
in either call emptied the value, the guard declined, and the false
comment posted with nothing in the log separating "the guard ran,
nothing matched" from "the lookup died" — while every sibling lookup in
these steps announces its failures. Both unavailable paths now emit a
`:⚠️:` and a step-summary line before posting. No behavior
change: posting was, and remains, the fail-open direction.

Tests: a re-run fixture per site, where the stub answers `createdAt` and
`startedAt` with DIFFERENT values and attempt 1's review sits between
them — reverting either site to `startedAt` fails exactly these two; and
a per-site assertion that both unavailable paths announce themselves.

Also from round 1, both verified before taking: the stub's standalone
`*state*)` branch is dead (no `--json state` call remains in either
extracted step) and is removed, so its scenarios cannot be edited into a
no-op; and the harness now substitutes `${{ vars.* }}` before running
the in-job script, which bash rejected as a bad substitution — the
assignment was skipped, `MAX_TIMEOUT_MINUTES` stayed unset, and eight
error lines rode every suite run, so "the step's real bash" was not
quite true for that line.

* fix(ci): read the head this run reviewed, and claim only what the guard proved

Round 2's six, all taken.

The fallback JOB compared review commit ids against the PR's head at
fallback time, not the head the run reviewed. On every trigger but
pull_request_target the head-moved guard above deliberately does not
run, so a push landing between the post and this step leaves that value
pointing at bytes no review ever covered: the match fails and the
contradictory comment posts anyway — the #9342 shape, re-opened for the
trigger + post + push + fail-after-post interleaving. `review-pr` now
publishes the head its review step recorded as a job output, and the
guard reads it, falling back to the fresh head only when the job died
before that step (a run that posted nothing either). The in-job twin
needs none of this — its unconditional head-moved check exits first —
and that asymmetry is now pinned per site rather than left to be
rediscovered.

Both skip messages claimed "this run already posted a review". Reviews
carry no run id, so the window (bot account + head + submitted at or
after this run was created) also matches an overlapping sibling run's
review, which this workflow's own concurrency note says can happen. The
suppression is right either way — a review IS sitting above the comment
— but the oncall reading the summary was told something the guard never
proved; both now say what it did.

The guard's opening paragraphs still described the round-1 `startedAt`
anchor while the code (and the paragraph below it, and the runtime
warning) said creation. A maintainer reading top-down got the anchor
that re-runs break — the defect round 1 removed.

Test stub: `gh run view` now answers by running the caller's own --jq
over an object carrying both timestamps, instead of a `case` on "$*"
that matched substrings in order. A combined
`--json createdAt,startedAt --jq '.startedAt'` was answered from the
createdAt branch, leaving the re-run pin green for a guard reading the
attempt-scoped field — the exact regression it exists to catch.

* fix(ci): attribute the guard by time alone — the head is not a stable run attribute

Round 3's blocker, and the second time the head clause re-opened the
contradiction this PR exists to close. Two entrances this round, both
after a "Re-run failed jobs": attempt 2 dies before the review step
writes its head, so the guard falls back to a head attempt 1 never
reviewed; or a push lands and attempt 2 records the NEW head — in both,
attempt 1's own review no longer matches `.commit_id`, and the fallback
posts "failed before a review could be posted … retry" beneath the
review the same run had posted.

Rather than patch the head lookup a third time, the head clause is
gone. What the guard proves is now narrower and stable: a bot review of
this PR was submitted while this run was alive — bot account plus the
attempt-stable `createdAt` window. That closes both entrances at once
and takes the round-2 cross-job wiring with it (review-pr's
`expected_head_sha` output and the env line that read it), so there is
no untested chain left whose silent breakage would restore the
fresh-head comparison. The job-level step no longer needs the PR head
either and reverts to its state-only query; the test stub's
state-only branch, removed in round 1 as dead, has a caller again.

The comment blocks now state the guarantee the concurrency model
actually supports. They claimed a review inside the window "cannot
belong to an earlier run", but per-run concurrency groups deliberately
allow overlapping runs on the same head, so an earlier-created run's
review can match and this run's failure then goes unannounced. That is
accepted, and said plainly: the silence coincides with a bot review a
reader can see — the very state that makes the comment's claim false —
while the bot-author and creation-time clauses still rule out silence
with no review at all.

Tests: the moved-head case flips from "posts" to "silences" and is
pinned per site (a review on ANY head inside the window silences);
re-introducing a head clause fails exactly that test; and a structural
pin asserts the wiring is absent rather than merely unused.

* test(ci): skip the guard's jq-driven cases where jq is absent, instead of failing them

The stub answers the guard's reviews and run-view lookups by running the
caller's own `--jq` filter — that filter IS the thing under test — so
those cases need jq on PATH. A reviewer running the suite on Windows
without jq saw them as failures of the guard rather than as untested,
which is the wrong signal in the wrong direction.

Probed once per run and skipped honestly. Measured with a jq that exits
127: the file goes from 31 failures to 26 failures plus 13 skips — the
26 are the retry-loop cases, which have parsed the review log with jq
since long before this change and are equally untestable without it.
GitHub's windows-latest image ships jq, so CI coverage is unchanged
either way; what changes is what a jq-less machine reports.

* docs(ci): remove the head-keyed leftovers the guard no longer has

Round 5's four, all leftovers of the round-3 design change rather than
new behavior.

The job-level block still explained why it compared against the head
this run reviewed — naming `pr_head`, "the reviewed head's review" and a
`review-pr` job output, none of which survive: the shipped filter is
author scope plus the creation-time window, and the wiring was deleted
with the head clause. A maintainer reading it would look for a
comparison that is not there. The in-job block stated the
createdAt-not-startedAt rationale twice, once with the measured run ids
and once without; the measured one stays.

Same in the tests: the stub's comment listed a head clause the filter
deliberately does not have (`attributes by TIME, not by head` is the
test that pins its absence), and the harness still declared and injected
`reviewedHead`/`REVIEWED_HEAD_SHA`, which nothing reads since the wiring
went — a knob that looks live and cannot be.

* docs(ci): drop the duplicated anchor rationale and the last stale-head leftovers

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

* fix(ci): exclude foreign same-account approvals from the already-posted guard

* fix(ci): attribute the already-posted guard by composed-review markers

The foreign-approval exclusion list shipped incomplete: the triage skill's
commit-pinned APPROVE body also posts under the same account, matches the
guard's author and window clauses, and silenced the fallback for a
genuinely dead run — the failure shape this guard exists to stop. The
producer set is open, so no exclusion list can be finished; every miss
fails in the dangerous direction.

Match positively instead: a review silences the fallback only if its body
carries what only this pipeline's composed reviews carry — the
"via Qwen Code /review" attribution footer or the invisible
qwen-review-ledger marker. Every composed body carries at least one (a
zero-findings APPROVE included); no foreign approval carries either. A
marker that ever changes shape stops the guard firing and the comment
posts — the pre-guard status quo, not a masked dead run.

---------

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-20 23:51:05 +00:00
qwen-code-dev-bot
6073afbdb1 Merge remote-tracking branch 'origin/main' into fix/revive-platform-lanes
# Conflicts:
#	.github/workflows/ci.yml
2026-08-20 16:38:38 +00:00
Shaojin Wen
c59910ba3f
fix(ci): make autofix finding replies idempotent (#9463)
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
npm cache producer / Save npm cache (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (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
Security Checks / Dependency CVE audit (push) Waiting to run
Security Checks / Secret scan (TruffleHog) (push) Waiting to run
* fix(ci): make autofix finding replies idempotent

A crash-and-rerun of an address round, a same-run repair that regenerates the dispositions, or a later round re-declining the same finding all reproduce the same comment-replies.json entry — and the reply step posted it again, landing identical bot replies on one thread (observed 2026-08-16: one identical reply posted three times, #9296).

The thread fetch now also reads each comment's author and body, and the reply step skips posting when the thread already carries a comment by the autofix bot whose body equals the neutralised body about to be posted. A changed body — new information from a later round — still posts; a threads view without author/body, or a stale/empty one, degrades to the old post-always behavior. The replies API itself is already the no-review-event path, so this PR only adds the missing idempotence (the P1 replies item of #9296).

Refs #9296

* fix(ci): restore inner pageInfo in autofix threads query and pin reply-gate contracts (#9463)

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-08-20 14:10:26 +00:00
Shaojin Wen
4807d06b31
fix(autofix): mutation-probe new guards before a round commits (#9578)
* fix(autofix): mutation-probe new guards before a round commits

Provenance analysis of six multi-round takeover PRs found roughly a
third of post-initial review findings were introduced by the
immediately preceding fix round, and the dominant shape was guards
and branches added with no test of their own: the deterministic gate
re-runs only the tests that exist, so an unwitnessed guard passes
every gate and its hole resurfaces as a new finding in a later
round. Require the agent to prove each newly added guard or branch
kills a test (mutation probe) before committing, and record the
probe in the round summary.

* fix(autofix): pin the mutation-probe mandate, not just its intro

The original pin stopped at the preposition "before", so a future
edit that kept the intro phrase but gutted the remove/confirm-FAIL/
restore procedure would leave the contract test green. Pin both
halves of the mandate. Mutation-verified: gutting the mandate in
SKILL.md now fails the skill contract test.

* fix(autofix): also pin the mutation-probe remediation clause

The procedure pins cover what the probe does, but not what to do
when it exposes an unwitnessed guard: dropping the remediation
sentence ("write a test that pins it (or drop the guard)") left the
whole suite green while the rule told the agent to probe without a
verdict path. Pin the remediation clause too; mutation-verified.
2026-08-20 14:03:00 +00:00
Shaojin Wen
099a71c936
fix(ci): heal a symlinked workspace instead of wedging the runner on it (#9498)
* fix(ci): heal a symlinked workspace instead of wedging the runner on it

The hardened wipe guard refuses any workspace that canonicalizes outside
the runner workspace. That refusal is correct, and it created a permanent
failure: when a previous job leaves the workspace replaced by a symlink
pointing outside — or by any non-directory — the guard resolves it to the
target, refuses, and exits 1 having removed nothing. Nothing else clears
that state, so every later job on the runner dies at the same line,
forever. The pre-guard code wiped through the link and self-healed by
accident. Reproduced against main's own step text before this change.

Heal it: the link itself lives inside the runner workspace and is safe to
unlink, and only once it is gone can a legitimate wipe proceed.

The layer has to sit before canonicalization — afterwards the path has
already resolved to the target and the allowlist refuses before any repair
can happen — which means it judges a raw path, and that is where the first
attempt at this (closed with #9369) went wrong. A raw `"$RWS"/*` match
accepts `$RWS/link/sub` as a string while the kernel resolves it through
an intermediate symlink to a file outside the runner workspace, so the
unlink and the mkdir landed outside and only then did the allowlist refuse
the wipe. Here the containment is judged on the canonicalized PARENT —
never on $WS, which would resolve through the very link being removed —
and the unlink then acts on the raw path, so it takes the link and never
follows it.

Four more constraints the same review surfaced: the raw trailing-slash
strip moves ahead of the predicates (both `[ -L "$WS/" ]` and
`[ ! -d "$WS/" ]` resolve through a link and report its target, so one
slash hides the corruption); the allowlist root is prepared before the
heal, since it bounds it, and an empty $RUNNER_WORKSPACE would degenerate
the containment pattern to the match-all `/*`; both the unlink and the
mkdir fail closed, because under `-e` a failure that is not the last
command of an && list is swallowed and would leave the wipe running on a
corrupt path; and the heal logs what it found and where the link pointed,
since this incident otherwise leaves no trace at all.

All three copies get it — the two triage wipes and the A/B wipe — with
per-suite fixtures: the wedge healed (link gone, directory recreated,
target's contents intact), the intermediate-symlink attack refused with
the outside file unmutated and zero rm calls, the non-directory half, the
trailing-slash spelling, the fail-closed unlink, and the ordinary
workspace where the heal must not fire at all. Mutation-checked layer by
layer; each has a fixture that fails when it is removed.

One pre-existing test changes meaning: the canonicalization pin used a
symlinked workspace and asserted refusal, which is now the healed path. It
moves to a vector the heal does not touch — an intermediate symlink whose
far end is a directory — and keeps its mutation strength: with the
canonicalization deleted, find resolves the link and hands the outside
directory's entries to the rm recorder.

Closes #9480

* fix(ci): keep the heal's log out of the workflow-command channel

Three findings from the first review round on this layer.

The heal logged the symlink's target inside a `:⚠️:` line. The
target is bytes a PREVIOUS job chose — on the verify lane that job may
have run a contributor's code — and the runner parses `::` at the start
of any stdout line as a workflow command, so a target of
$'…\n::error::forged' let the step reporting the corruption forge an
annotation. The annotation now carries no untrusted bytes: the target is
stripped of line breaks, capped, and printed on its own prefixed line,
where a leading `::` cannot begin a command. Verified against the real
step text — the forged line lands as data, and no output line starts with
`::error::`.

The mkdir leg's refusal had no executed fixture while its `rm -f` sibling
had one. It does not need a permission trick: `rm -f` returns 0 for a
path whose parent is not a directory (it reads as "already absent"), and
the mkdir that follows cannot succeed — so the branch is reachable, and a
swallowed failure there would run the wipe against a path that does not
exist. Fixtures in both suites, and it runs as root too.

And the post-run triage copy's header still said this copy "predates the
checkout-heal hardening and never received it" while carrying the whole
guard plus the heal directly underneath. That header is the in-code
inventory the eventual convergence of these copies will read; understating
it is how a sync strips layers in the wrong direction.

* test(ci): drive both wipe copies in the remaining single-step heal fixtures

* fix(ci): keep the Serve A/B job from timing out on slow runners

---------

Co-authored-by: Qwen Autofix <autofix@qwen-code.dev>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-20 13:54:36 +00:00
Shaojin Wen
6fe9ce4886
fix(ci): stop counting wedged queued runs as in-flight in the shepherd (#9518)
* fix(ci): stop counting wedged queued runs as in-flight in the shepherd

When GitHub refuses to start a workflow run it still CREATES it: the run sits
`queued` forever with zero jobs and cannot be cancelled or deleted through the
API. On 2026-08-19 an oversized qwen-autofix.yml produced a run like that from
the shepherd's own liveness dispatch, and the watchdog counted it as in-flight
for the next 18 hours:

  last scan signal: 2026-08-19T05:01:14Z (1107m ago), in-flight: 1

The age gate said "dispatch a scan", the in-flight gate said "one is already
running", and nothing ever completed the run that would clear it. The loop
stayed dark until a human looked.

Treat a run still `queued` past ZOMBIE_QUEUED_MINUTES (30, overridable via the
QWEN_SHEPHERD_ZOMBIE_QUEUED_MINUTES repository variable) as wedged rather than
live. One `wedged` predicate is defined once and reused by the in-flight count,
the conflict lever's busy-set, and a new census, so the three readers cannot
disagree. Only `queued` runs wedge — a review-address run legitimately runs for
hours — and a missing createdAt reads as brand new, so unknown age never
licenses a duplicate dispatch.

The wedge is now visible instead of silent: a :⚠️: names the count and
the oldest one, the tick heartbeat carries `wedged-queued:`, and the dashboard
carries a banner. Invisibility is what made this expensive — PR-event runs kept
reporting success while every scheduled scan was dead.

Verified against the real run list from the incident: the old predicate returns
in-flight=1 (starved), the new one returns 0 with a census of 2. Behavioral
tests replay both jq programs and the busy-set walk verbatim from the workflow.

* fix(ci): keep shepherd busy-set job-verified and bound wedge re-dispatch (#9518)

* fix(ci): reject degenerate zombie threshold and name the paused liveness gate (#9518)

* fix(ci): name the recorded liveness run in the shepherd wedge remedy (#9518)

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
2026-08-20 13:37:22 +00:00
Shaojin Wen
bb599f6506
Merge branch 'main' into fix/revive-platform-lanes 2026-08-20 20:33:35 +08:00
Shaojin Wen
313f191150
fix(autofix): make the brake's BLOCKED handoff a first-class round outcome (#9297)
* fix(autofix): make the brake's BLOCKED handoff a first-class round outcome

When the growth brake fires, feedback.md tells the address agent to stop BLOCKED with a handoff — but the output contract only accepted address-summary.md or no-action.md, so a round that followed the instruction died as 'finished without required output file(s)', the brake's decision text was buried under a generic failure.md, the report said 'could not produce a passing fix', and the job left a red review-address check that the next scan counts as new feedback. Observed on #9222 rounds 6/7.

The handoff becomes a first-class verdict end to end: run-agent.mjs honors an agent-written handoff.md (with no fix verdict) as a graceful exit the way it already honors failure.md, and shields it from the API-error retry reclassification; the verification gate reports outcome=handoff for a no-commit round with a handoff and no failure.md; finalize lets handoff pass without failing the job; the report step runs for this outcome, posts the handoff note with the eval marker (watermark advances — the feedback is consumed as evaluated), and names the stop honestly instead of reporting it as a failed fix. The skill now tells the agent exactly which file to write when the brake fires. A coexisting spec output still outranks the handoff, and failure.md coexistence keeps the failed classification, so crash paths are unchanged.

* fix(autofix): align the handoff outcome's consumers and pins with its contract (#9297)

Review found the new handoff outcome breaking two pinned helper tests
(stale breaker-headline wording, unclassified headline in the fleet-shepherd
contract test), misreporting handoff rounds in the status-comment finalize
step, and leaving the whole handoff chain unpinned against mutation.

- Update the breaker headline pin to the PR's reworded headline.
- Classify the handoff headline as transient in the shepherd contract test
  and drop its "AutoFix stopped" prefix so the shepherd's terminal-only
  REASON regex cannot capture a transient stop (the shepherd workflow itself
  stays outside this round's footprint).
- Include handoff in the Finalize-status published-report branch.
- Give deliberate stops their own takeover-digest census bucket instead of
  the residual crash/infra bucket (EN + ZH).
- Neutralize :: workflow commands at the two new handoff echo sites.
- Use the runner's non-empty missing() convention for handoff.md so an
  empty file cannot read as a verdict in one layer and not the other.
- Correct the run-agent.mjs precedence comment: when a handoff coexists
  with a spec output, the gate (handoff branch first) decides the round,
  matching the documented "handoff + no-action -> handoff" contract.
- Pin the handoff chain where its siblings are pinned: finalize replay,
  POST_HANDOFF replay, mark/headline replays, the gate's no-commit decision
  table, the stub-runner handoff/empty/API-error cases, the report-step
  if-clause, and the census needle-to-emit cross-pins.

* fix(autofix): classify a no-commit handoff before the gate's structural checks (#9297)

Review proved the new handoff classification unreachable exactly where
the brake fires: the structural pre-checks (core rebuild, settings
schema, contracts) judge the PR's own diff and reject before the
no-commit fork, and the growth brake fires on precisely the red PRs
whose diff trips them. A compliant handoff (no commit, only handoff.md)
then classified as a retryable failure, so the repair pass deleted
handoff.md and could commit against the brake's explicit stop — the
self-feeding loop the handoff exists to prevent. Reproduced with the
real gate script: schema-check-fail + no-commit handoff exited 1 with
no outcome=handoff.

Move the no-commit handoff classification above the structural checks
(right after the failure.md exits, which keep their precedence). A
handoff claims nothing — acted=false, deferred to a human — so the
checks' false-no-action rationale does not apply, and the retryable/
repair machinery must never engage on a round the brake told to stop.
The no-op fork reverts to no-action-only classification.

- Add a gate test: stale schema + no commit + handoff.md classifies
  outcome=handoff, exit 0, no retryable (fails on the pre-fix gate).
- Pin the handoff-note :: workflow-command neutralization in both
  layers (the gate's sed and the runner's replaceAll), which review
  showed were surviving mutations.

* fix(autofix): reject a no-commit handoff written over a dirty workspace (#9297)

* fix(autofix): report a dirty-handoff rejection honestly, not as a failed fix (#9297)

* fix(autofix): reject a handoff written beside a round commit, non-retryably (#9297)

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

* fix(ci): classify the committed handoff shape as its own non-retryable outcome

R7-1 on this PR: a round that HAS a commit beside handoff.md skipped both brake-violation guards (clean tree misses the dirty guard; committed ref misses the no-commit branch) and fell through to the structural checks, where reject_fix defaults to retryable and the repair pass deletes handoff.md and may commit again against the brake's stop.

Classify it before the structural checks under its own outcome committed_handoff, sibling of dirty_handoff: non-retryable, its own honest report headline (reusing dirty_handoff's wording would claim nothing was committed when a commit exists), listed among the report-publishing outcomes in the status classifier, and never routed through finalize's pass list. Pins updated in the same pass: the committedWithHandoff gate case now expects committed_handoff with no retryable, the shepherd contract test classifies the new headline as transient (loop stays engaged), the status-classifier pin names all five outcomes, and the handoff-contract gate test gets an explicit subprocess budget (eight fixture arms outgrew the 5s default).

* fix(ci): count committed-handoff rounds in the milestone census rejected bucket (#9297)

* fix(autofix): publish brake violations green and preserve handoffs across crashes (#9297)

Two Critical review findings on the handoff output contract.

Brake-violation rounds (dirty_handoff / committed_handoff) ended with a
red review-address check: the eval marker stamps ts=NEWEST, strictly
before the check completes, and the scan counts failed checks completed
after the watermark — including this workflow's own review-address
checks — as new feedback. The next scan re-selected the PR and burned a
full agent round on the item the posted headline promised not to retry,
once per violation. Admit both outcomes to the green finalize arm the
way the clean handoff already is (the diff's own comment names this
self-feeding loop as the reason handoff went green), and key the report
step's routing and POST_HANDOFF trigger on the outcomes themselves so
the green rounds still publish their honest headline, handoff note, and
eval marker instead of going silent.

A crash, budget kill, or loop guard after the agent wrote handoff.md
synthesized a failure.md that shadowed the note: the gate reads
failure.md first (outcome=failed), the report preferred it, and the
timeout sentinel re-handed the item the brake stopped. Preserve the
agent-written handoff in the crash branch (exit 0, mirroring the
agent-written-failure.md arm), and never let writeHandoff overwrite a
non-empty agent verdict.

Both findings reproduced against this commit's verbatim code before
fixing: the case/jq replay showed the violation check red and counted
as new feedback, and a stub run showed the synthesized failure.md
shadowing the handoff. New behavioral tests fail pre-fix and pass
post-fix.

* test(ci): give four subprocess-heavy replays explicit budgets

The milestone digest, stale-duplicate revalidation, deny-by-default footprint, and recoverable-API-render tests spawn multiple bash replays of the real workflow/gate scripts each; the files those replays parse grew with this PR's handoff chain, and all four outgrew the 5s default (each verified to pass with an explicit 30s budget, matching the suite's convention for subprocess-heavy tests).

* fix(ci): mirror the handoff outcome consumers into the recovery clone (#9297)

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-20 08:32:43 +00:00
易良
0baaec2b32
chore(ci): Drop NPM_TOKEN in favor of npm Trusted Publishing (#9552)
* chore(ci): Drop NPM_TOKEN in favor of npm Trusted Publishing

* chore(ci): Pin npm 11 for Trusted Publishing in release jobs

* test(ci): Cover Trusted Publishing requirements
2026-08-20 08:31:41 +00:00
Shaojin Wen
4fefc81472
Merge branch 'main' into fix/revive-platform-lanes 2026-08-20 14:36:26 +08:00
易良
48b30647d0
refactor: centralize cross-package contracts (#9497)
* refactor: centralize cross-package contracts

* fix(build): harden cross-package contract checks

* docs(core): clarify sub-session prompt limit scope
2026-08-20 06:24:41 +00:00
易良
b219e3a716
chore(ci): Add --provenance to npm publish and id-token permission (#9532)
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
Security Checks / Dependency CVE audit (push) Waiting to run
Security Checks / Secret scan (TruffleHog) (push) Waiting to run
* chore(ci): Add --provenance to npm publish and id-token permission

* test(scripts): expect --provenance in npm publish step assertion

PR #9532 adds --provenance to every npm publish in the release pipeline.
Update the workflow-pinning test to match the new command so the helper
test suite stays green.
2026-08-20 05:21:02 +00:00
Shaojin Wen
3b3818db87
fix(ci): keep qwen-autofix.yml under GitHub's 500 KB start-runs limit (#9517)
GitHub does not start runs for a workflow file larger than 500 KB (512,000
bytes) and reports nothing when it stops. qwen-autofix.yml crossed that line
on 2026-08-19 at 512,782 bytes: schedule ticks stopped firing, every
workflow_dispatch sat "queued" forever with zero jobs and could not be
cancelled, and issues/issue_comment went quiet — while pull_request_review
runs kept succeeding, because a PR event resolves the workflow from the PR's
own branch and those carry older, smaller copies of this file. The loop
therefore looked half-alive and stayed dark for a day.

Move 75 long comment blocks (1,326 lines) verbatim into a sibling design
record, .github/workflows/qwen-autofix.md, leaving each block's opening lines
plus a `qwen-autofix.md#af-NNN` pointer where it sat: 518,055 -> 426,437
bytes. No executable line changes — the YAML parses to an identical document
outside `run:`, every `run:` script still passes `bash -n`, and the only lines
removed anywhere are comments. Steps that are duplicated verbatim across jobs
share one pointer so they stay byte-identical.

Add .github/scripts/check-workflow-size.sh (gate at 470,000 bytes), wired into
CI on every profile: a .github-only PR classifies as `github_ci_only` and
skips the `full`-only checks, which is exactly the PR that can trip this.
Tests pin the gate, every workflow's size, and pointer/section symmetry.

Delete qwen-autofix-recovery.yml. It was cloned during the incident on the
theory that the workflow ENTITY was wedged, but it carried the same oversized
file, so its dispatches queued identically and its schedule never fired.
2026-08-20 01:56:45 +00:00
wenshao
b9de49df20 Merge branch 'main' into fix/revive-platform-lanes
Conflict in the autofix suite: main changed the bite-check test's resolver
stub while this branch had wrapped the same test in the bash-mapfile host
probe. Resolved by taking main's body and re-applying the probe wrapper, so
neither side's intent is lost.

Also fixes a load failure this branch carried: the mktemp host probe added
for the macOS lane calls spawnSync, which the review suite never imported —
the whole file threw ReferenceError at collection, taking its 156 tests with
it. Imported alongside execFileSync.
2026-08-20 00:47:26 +08:00
Shaojin Wen
133cf8bfcf
refactor(cli): consolidate shared helpers ahead of the legacy audit skill (#9345)
* refactor(cli): consolidate shared helpers ahead of the legacy audit skill

Move the pieces the upcoming /audit skill needs out of command-group
ownership so no skill imports across command groups:

- the findings schema moves from commands/review/ to cli/src/utils/ as-is;
  every review consumer imports it from the new home, and the stale-bundle
  digest, bundle-asset list, and artifact comment track the move
- safeTarget (traversal-safe slug) and tokenizeArgs (quoted argument
  splitting) lift to cli/src/utils/paths.ts and shell-args.ts, with
  review's copies re-exporting/redirecting
- the two private git check-ignore copies (review test-plan, team memory)
  consolidate into one fresh-by-default helper in core utils; the memo
  stays caller-side so a remedy re-check observes the flip

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

* fix(cli): restore review ledger machinery dropped by the consolidation

The shared-helper consolidation silently reverted three behaviors PR #9175
added to compose-review, against the PR's stated "no behavior change"
intent: the unreviewed-dimension anchor exemption (scopeUnproven /
dimensionGapsAreDepthOnly / isNonDiffDimensionGap), the LEDGER_MAX_ROUND
stamp clamp, and the bilingual budget-stop phrase splice. Restore them with
the tests that pin them; SKILL.md, ledger.ts, and deadline.ts still
document all three.

Also harden the new helper tests:
- safeTarget: the deep-path fixtures now share a flattened prefix longer
  than the kept window, so a truncation-only slug (no digest) collides
  instead of shipping green.
- isGitIgnored: each GIT_* scrub arm now carries a discriminating fixture
  (three arms previously passed with their scrub line deleted), and every
  foreign git init scrubs ambient repo-placement selectors.

* fix(core): scrub git config-injection channels from the ignore probe

Ambient GIT_CONFIG_COUNT (inline KEY/VALUE injection) and
GIT_CONFIG_GLOBAL/GIT_CONFIG_SYSTEM (config-file redirects) can aim
core.excludesFile at a foreign rules file and flip the probe's verdict
for the -C worktree — the same leak class the existing selector scrubs
close. Measured on the pristine probe: both channels turn a
not-ignored path into an ignored one.

Also pin the guards the review found unpinned: the two config
channels, the `--` separator for dash-leading paths, the timeoutMs
wiring, safeTarget's hash-of-original-target property, and
tree-existence of the lifted review helpers.

* fix(core): scrub pathspec-magic env channels from the ignore probe

* fix(core): drop the whole GIT_* env family from the ignore probe

Two more leak channels surfaced on the probe's per-variable scrub list:
GIT_ICASE_PATHSPECS (the fourth pathspec modifier — ambient, makes
check-ignore reject every pathspec with exit 128, which the catch reads
as not-ignored) and GIT_CONFIG_PARAMETERS (the inline -c channel git
itself uses to propagate config to children — ambient, can aim
core.excludesFile at a foreign rules file). Measured through the real
function: the first flips a genuinely ignored path to not-ignored, the
second flips a not-ignored path to ignored.

Since the channel list grew by one leak per review round, drop the
whole GIT_* family instead of enumerating, and close the system config
tier explicitly (GIT_CONFIG_NOSYSTEM=1) so host policy in
/etc/gitconfig can no longer answer for the -C worktree — that ambient
dependency also made the config-redirect arm red on any host whose
system config matches the probe path. Pin the icase member, the
PARAMETERS channel, the default 5 s deadline (previously unpinned), and
add a lower timing bound to the caller-deadline arm so it cannot pass
vacuously when the shim is not executable.

* fix(cli): make the safeTarget slug space prefix-free at the dash boundary

Review's cleanup sweeps .qwen/tmp/ by qwen-review-<slug>- prefix. A slug
that itself carried '-' — natively (pr-6771 vs pr) or via the truncation
join — could extend a shorter slug, letting one target's cleanup delete a
DISTINCT target's artifacts (R8-7). The truncation branch this PR carries
newly lands deep targets inside the cap instead of dying ENAMETOOLONG,
which turned the latent collision live.

Drop '-' from the slug alphabet entirely (dashes flatten like separators)
and join the truncation digest with '_': with '-' out of every slug, the
qwen-review-<slug>- boundary is unambiguous by construction — no slug can
start with another slug plus '-'. prev-ledger side files keep their
hardcoded dashed name on both writer and reader, untouched by the slug.

Tests pin the prefix-free property (short-vs-short, short-vs-truncated)
and the fixture names follow the new slugs.

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

* revert(cli): restore safeTarget byte-identical to the pre-lift behavior

Reverts the slug alphabet change (539495226d) and drops the deep-target
truncation branch, per review round findings R12-1 and R8-7:

- R12-1: flattening dashes renamed only one side of review's two-sided
  artifact-naming contract — bundled-skill templates, composed names,
  prev-ledger and brief/report producers hardcode the dash spelling, so
  the bypass-audit tripwire would silently skip and the cleanup sweep
  would leak ~15 dash-form artifacts per review.
- R8-7: the prefix-sweep hazard only exists because of the truncation
  branch this lift carried; main's safeTarget has no truncation, so a
  behavior-preserving lift must not add it. Deep-target support belongs
  in a follow-up paired with the sweep-side structural fix it needs.

safeTarget is now byte-identical to the pre-lift implementation (moved,
not modified); tests pin the dash spelling and the leading-strip rule as
they behave on main. Full review suite green (5022).

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

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.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-19 14:53:44 +00:00
易良
b5577b7d11
fix(sdk): route unrecognized diagnostics onto a bounded transcript sidechannel (#9202)
* fix(sdk): route unrecognized diagnostics onto a bounded transcript sidechannel

Normalizer-classified unrecognized_event / unrecognized_session_update debug events no longer enter transcript blocks[]: they are mirrored onto a capped unrecognizedDiagnostics sidechannel instead. This stops them from finalizing a streaming assistant/thought block (which dropped a following assistant.usage frame) and from consuming the maxBlocks budget (which let repeated noise evict real conversation content). malformed_payload diagnostics and client-dispatched debug events keep their existing block semantics.

* fix(sdk): align browser bundle budget

* fix(sdk): close the sidechannel review round (#8823)

- export the sidechannel API through the daemon barrel
  (selectUnrecognizedDiagnostics, UNRECOGNIZED_DIAGNOSTICS_LIMIT,
  DAEMON_UI_UNRECOGNIZED_DIAGNOSTIC_REASONS + types) and pin the
  reachability in daemon-public-surface.test.ts
- restore the MAX_TEXT_BLOCK_LENGTH cap on sidechannel text, mirroring
  truncateText exactly (suffix fits within the cap)
- ship the unrecognized reason subset as a runtime const array and route
  by membership, so a new reason cannot fall through to appendStatusBlock
- copy the correlation fields createBase stamps (promptId, sourceRecordIds,
  branchRecordId, originatorClientId) onto sidechannel entries; drop the
  dead source/data switches
- un-fuse the budget-history comment chain in scripts/build.js
- update docs/developers/daemon-ui for the split routing
- tests: full entry shape, text cap, block-path debugReason counterpart,
  and a webui malformed_payload interleave sibling so the #7012
  flush-before-guard keeps a discriminating stimulus

* fix(sdk): address round-2 sidechannel review for #8823

- build.js: bump daemon browser bundle budget 191KB -> 192KB
  (195,591 bytes measured > 195,584 cap; build failed at head)
- webui: narrow the observer-mode debug guard so unrecognized_*
  diagnostics reach the reducer sidechannel; only block-path debug
  events are dropped
- webui: merge history-store unrecognizedDiagnostics in
  applyTranscriptHistory so paged-back sessions keep diagnostics
- transcript: extract truncateTextAtLimit shared by the block and
  sidechannel truncation paths
- transcript: reset unrecognizedDiagnostics on rewind alongside the
  sibling per-turn state resets
- types: rename DaemonUnrecognizedDiagnostic.receivedAt to
  clientReceivedAt (matches the sibling block projection)
- tests: reason-prefix conformance pin, rewind reset, narrowed guard,
  history pagination merge

* fix(webui): avoid flushing sidechannel diagnostics

* fix(sdk): preserve diagnostics across rewind

* fix(webui): dedupe sidechannel history records

* fix(webui): align the paging sidechannel test with the normalizer keys

The paging test added in e6b40e5c failed deterministically (webui
suite red, CI Test job red) for two reasons:

1. The fixtures stamped only _meta['qwen.session.recordId'], but the
   SDK normalizer's extractSourceRecordIds reads
   _meta.qwenTranscript.sourceRecordIds — no sidechannel entry ever
   carried sourceRecordIds, so the dedupe assertion could not pass and
   the new displayedRecordIds loop was never exercised by a passing
   test. Stamp BOTH keys, matching production replay frames
   (acp-bridge buildUpdateMeta) and the sibling dedupe test.
2. Cap arithmetic: LIMIT-1 live entries + 2 fresh history entries =
   LIMIT+1, so the newest-wins slice evicted record-old-1 which the
   test asserted present. Emit LIMIT-2 live events so the post-merge
   total lands exactly on the cap.

Also correct the post-merge index assertions: history entries come
first (old-1, old-2), then the deduped-once live overlap, then the
first live mystery event. Suite 506/506, eslint + prettier clean.

* fix(sdk): raise diagnostic sidechannel bundle budget

* fix(sdk): raise the daemon browser bundle budget to 198KB and pin the diagnostics selector

- The sidechannel routing + selector cost ~1037 B over the 197KB cap
  (bundle measured 201893 B), failing the browser-bundle size gate; bump
  MAX_DAEMON_BROWSER_BUNDLE_BYTES to 198 * 1024.
- Fold the rebase-residue 190→191→192 KB ledger entries into the accurate
  190→195→196→197→198 lineage so the next bump has one canonical history.
- Add a behavioral pin for selectUnrecognizedDiagnostics: it must return
  the routed sidechannel itself (toBe), discriminating a `return []` or
  shallow-copy regression that the typeof-only surface test cannot see;
  flip-verified.

* fix(sdk): reset the user pointer on sidechanneled diagnostics, share the routing predicate

appendUnrecognizedDiagnostic left activeUserBlockId untouched while the
replaced appendStatusBlock path reset it for every non-user block; a
later mergeable user.text.delta with no promptId stamp (e.g. a peer
client's $ <cmd> echo) then appended onto the earlier user block
across the diagnostic, collapsing two user turns into one and skewing
rewindTranscriptToUserTurn's kind==='user' turn indexing. Keep the
reset (assistant/thought pointers stay untouched, the point of the
sidechannel); witness test flip-verified red without the one-line reset.

Also export isUnrecognizedDiagnosticReason from types.ts next to
DAEMON_UI_UNRECOGNIZED_DIAGNOSTIC_REASONS and call it at all three
routing-guard sites (reducer, provider flush condition, provider drop
filter) so the #7012/#8823 guard pair classifies every debug event
against one source instead of three hand-written copies.

* fix(ci): prevent bite harness SIGPIPE

---------

Co-authored-by: yiliang114 <yiliang114@users.noreply.github.com>
2026-08-19 14:38:00 +00:00
wenshao
14d138d717 fix(ci): close the classifier's CRLF gap and widen the lane step scan
Three findings from this round, all in the direction of the tests and
the parser being less clever than they claimed.

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

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

And the nightly blast-radius guard tested for the MENTION of an
allowlisted event rather than the IMPOSSIBILITY of `schedule` — a job
gated `pull_request || schedule` satisfied it while running every night.
It now requires the absence of an explicit schedule clause too.
2026-08-19 22:13:47 +08:00
Heyang Wang
5003ab3c7f
feat(web-shell): add transcript contract prevalidation (#9388)
* test(web-shell): add transcript contract prevalidation

Freeze reproducible evidence for current transcript paths before any
VS Code or HTML export production migration.

- Add versioned fixtures, closed export schema, and capability gates
- Probe direct-daemon and ACP identity under partial history prepend
- Preserve raw adapter semantics and full write_file Turn Output diffs
- Document the two-MR architecture, security constraints, and blockers

* fix(web-shell): harden transcript prevalidation gates

Make the evidence-only contract suite enforce the review assumptions it
documents while preserving the existing runtime transcript behavior.

- Run the contract suite in the required no-AK integration job
- Fail closed on ambiguous identity probes and deduplicate gate kinds
- Enforce manifest, hash, export safety, and renderer version boundaries
- Cover visible transcript text and stable Desktop packaging semantics
- Record the complete PR comment evaluation and verification outcome

* fix(web-shell): close transcript prevalidation gaps

* fix(web-shell): remove brittle Desktop wiring probe

Keep transcript contract prevalidation at the evidence level it can
actually prove. The previous source-text assertion could both reject
equivalent formatting and pass unreachable packaging code.

- Remove the Desktop script parser and its false behavioral claim
- Mark installed-artifact verification as deferred to Desktop smoke tests
- Clarify MR1 matrix, CI wiring, and provenance evidence boundaries
- Refresh the hash-locked capability matrix fixture

Note: This does not change Web Shell or Desktop production behavior.

---------

Co-authored-by: heyang.why <heyang.why@alibaba-inc.com>
2026-08-19 14:13:12 +00:00
wenshao
7f7223a002 test(ci): pin the gate as a disjunction and the watcher's name binding
Two mutation-survivable gaps in this PR's own tests.

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

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

Both checked by mutation: flipping one `||` and renaming the workflow
each turn a named test red.
2026-08-19 22:10:42 +08:00
wenshao
462962ef7c fix(ci): stop the subsystem rule matching compounds that name something else
The platform-sensitivity classifier split a path segment on dashes and
underscores anywhere, so `packages/web-shell/**` matched the `shell`
keyword — one of this repository's largest packages, a browser UI with
no host coupling, summoning both expensive lanes on every change to it.
That is the cost the gate exists to avoid, spent on the wrong diffs.

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

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

Also drops a wrong issue citation in the watcher test's comment: the
nightly comes from this change, not from the wipe-guard back-port.
2026-08-19 22:05:42 +08:00
Shaojin Wen
1eb8a0c7f8
feat(review): wire --resume through /review and the review run subcommand (#9153)
Surface the local resume feature (PR #9092) on the paths a user reaches
it from:

- `parse-args.ts`: `/review <pr> --resume` parses to
  `resume: { requested, effective }`, gated on PR targets (a local
  review's diff comes from a live working tree with no stable interrupted
  state). A `--resume` on a non-PR target warns and is inert.
- `run.ts`: the `qwen review run` headless wrapper takes `--resume` and
  passes it through to the `/review` prompt.
- `SKILL.md` Step 1 gains a "Resuming an interrupted run" branch: on
  `resume.effective`, append `--resume` to `fetch-pr`, branch on its
  `resumed` JSON, run `recover-findings`, re-enter the audit loop at
  `latestReverseAuditRound + 1`, and read the restart bound back from
  `restartsSpent`.
- `DESIGN.md` / `docs`: document resume as a LOCAL convenience.

The CI review workflow runs FRESH — it does not pass `--resume`. A CI
attempt runs no-sandbox on the reviewed PR's own code and its worktree is
deleted the moment it exits, so there is no interrupted state on disk for
a retry to continue; a resume would refuse `worktree-gone` and start over
anyway. The retry loop and its test assert the fresh-only wiring.
2026-08-19 05:13:09 +00:00
qqqys
b6e93d27ad
fix(autofix): paginate review threads instead of reaching the oldest 100 (#9390)
* fix(autofix): paginate review threads instead of reaching the oldest 100

`resolve_and_reply_threads` fetched `reviewThreads(first:100)` with no
pagination. GitHub returns review threads in ASCENDING creation order, so a
single page is the OLDEST hundred — on a long-running PR, precisely not the
threads the current round is answering.

Both blocks downstream map an inline-comment id to its thread. A thread past
the page is absent from `THREADS_JSON`, so an implemented Critical is never
resolved and reads as still open, and a declined finding's reply is answered
by silence. Those are the two outcomes the function exists to prevent.

Live: 8 of the 22 open takeover PRs exceed the cap. #8403 carries 1256
threads, so one page reached 8% of them — and all 1256 are unresolved.

The code already detected this: it requested `pageInfo{hasNextPage}` and
emitted a `:⚠️:` when true. It just never fetched the next page.

Use `gh api graphql --paginate`, which is built for exactly this shape, and
slurp its node stream into the flat array both blocks already expect. On
#8403 that is 13 requests in ~10s.

A partial fetch is USED rather than discarded: losing twelve good pages to a
rate limit on the thirteenth would resolve nothing at all, so the failure is
announced and the threads in hand still map.

One residual stays open and is now announced rather than implied: a thread
carrying more than 100 comments still truncates, so a comment past that page
is unmapped and each block falls back to the id as given. No thread in the
live pool comes close.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(autofix): keep the pagination failure's reason, and pin both warnings' absence

Round 1's three Suggestions, all on the partial-fetch path this PR adds.

R1-1: `2> /dev/null` on the paginated fetch discarded gh's stderr — the only
text saying WHY pagination stopped. The warning announced THAT it stopped, so
the oncall could not separate a transient rate limit (back off) from an
expired PAT (rotate) or a network failure without re-running the ~13-request
query by hand. Captured to `${WORKDIR}/threads-fetch.err` with the pattern
already used elsewhere in this workflow, and its tail folded into the warning.

R1-2: the outer thread pagination silently depends on the inner `comments`
pageInfo NOT asking for `endCursor` — gh's paginator adopts the first pageInfo
carrying both fields. The `Residual:` note actively invited a maintainer to
close that residual by adding it, which would hijack the thread-page cursor
and stop after page one at exit 0 with no warning, silently restoring the
oldest-hundred bug. Documented as load-bearing, in the comment block above the
fetch rather than inside the query literal — a `#` line there is transmitted.

R1-3: both new warnings were asserted only in the positive, so a mutation
making either unconditional shipped green. Added the clean-run absence
assertions this file's own convention calls for (321 `not.toContain` uses),
and the gh stub now writes a reason to stderr on failure so the folded-in text
is assertable.

Verified: qwen-autofix-workflow 178 passed. Mutation-checked — restoring
`2> /dev/null` and making the pagination warning unconditional each fail a
test. The one remaining failure (`behaviorally replays the stale-duplicate
revalidation`, 5s timeout) is identical with these changes stashed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(autofix): keep gh's error body out of the slurped review threads

Round 2 of the review on #9390 found the paginated review-thread fetch
poisons its own output on a partial page, and asked for two clarifications
around it.

R2-C (Critical) — on a failing page gh skips `--jq` and appends that page's
raw response body (a rate-limit message, or a GraphQL error envelope) to
stdout after the good nodes. The unfiltered `jq -s '.'` slurped it as an
extra element, and both consumers below iterate `.comments.nodes[]` over
every element, so the first one exited 5. This step runs under errexit, so
that aborted 'Push and report' AFTER a good push had landed — the report and
the markers were skipped and the job failed. That contradicts the two
invariants the block documents: a resolve failure must never fail a good
push, and a partial fetch is used rather than discarded. The slurp now keeps
only thread-shaped documents.

R2-1 — the comment block warned against adding `endCursor` to the inner
`comments` pageInfo, but the outer pageInfo's field ORDER is load-bearing for
the same reason: gh's cursor scanner carries its flags across pageInfo
objects and breaks at the first one yielding both fields, so alphabetizing to
`pageInfo{endCursor hasNextPage}` stops after page one just as silently. Said
so at the query, and at the test pin that goes red on a reorder, so the pin
is understood rather than bumped.

R2-2 — the stderr fold dropped the `tr '\r\n' '  '` that its ten sibling
sites apply. Actions parses workflow commands line by line and gh's
secondary-rate-limit stderr spans two lines, so the annotation kept only the
first — cutting off the words that separate a back-off from a credential
rotation.

Verification: `scripts/tests/qwen-autofix-workflow.test.js` 179/179; yaml
parses; eslint and prettier clean. Mutation-checked all three: reverting the
slurp filter fails the resolve arm with exit 5 (expected 5 to be 0),
reordering the outer pageInfo fails the field-order pin, and dropping the
`tr` fails the folded-reason arm on the second stderr line.

* docs(autofix): correct the field-order comment's mechanism (#9390 R3-1)

The comment explaining why `pageInfo{hasNextPage endCursor}` order is
load-bearing described the silent stop as happening with the carried
`hasNextPage` "already true" from the last inner page. That cannot
produce the symptom: gh's `findEndCursor` returns a cursor only `if
hasNextPage`, so a carried true would keep the walk going.

The real mechanism is the opposite one. The scanner carries its flags
across `pageInfo` objects and breaks at the first point both have been
seen; under `pageInfo{endCursor hasNextPage}` that break lands on the
outer `endCursor` while `hasNextPage` still holds the last INNER page's
value — almost always false, since thread comment pages rarely truncate
— and the outer page's own `hasNextPage` is never read. gh returns no
cursor and the walk stops after page one, exit 0 and silent.

Reworded in both places the clause was copied to: the workflow comment
and the field-order pin's comment in the test. No assertion, no shell,
and no query text changes; `pageInfo{hasNextPage endCursor}` and the
test that pins it are untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(autofix): keep gh's thread-fetch stderr off a predictable WORKDIR path

R4-1 (Critical, #9390): the review-thread pagination wrote gh's stderr to
`${WORKDIR}/threads-fetch.err` and read it back with `tail -c 300`, both
without a file-type guard. WORKDIR (`/tmp/autofix-review-<pr>`) is
bind-mounted read-write into the agent docker sandbox, and the round that
just finished ran branch code inside that sandbox, so the name is
attacker-chosen by the time this step runs.

A planted FIFO makes bash block on the O_WRONLY open before gh even execs,
and the only reader is the `tail` that runs strictly after gh returns — so
the step hangs to the job timeout with the push already landed, losing the
report comment and the round markers. That breaks the invariant this block
states for itself: a resolve failure must never fail a good push. A planted
symlink instead turns the redirect into a truncate/write against the link
target and the tail into a 300-byte arbitrary-file read folded into a public
`:⚠️:`.

Route the stderr through a fresh `mktemp` regular file instead, matching the
`gh api user` checks elsewhere in this workflow, and remove it afterwards.
The diagnostic is unchanged: the warning still carries gh's own reason, which
is the only text separating a transient rate limit from an expired PAT.

Test: plant a symlink at the old path, run the block through a failing fetch,
and assert the target's bytes are neither overwritten nor folded into the
annotation; plus assert the named path is not created at all. Mutation-
verified — restoring the `${WORKDIR}` redirect turns the canary assertion red
(`expected 'threads-fetch stub failure' to be 'CANARY-MUST-SURVIVE'`). The
FIFO half cannot be written as a plain assertion because the pre-fix code
hangs rather than fails; the same "named path is never opened" property
defuses it.

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 01:32:46 +00:00
Shaojin Wen
bc2d205d29
refactor(ci): simplify the review checkout self-heal back to wipe-and-retry (#9327)
* refactor(ci): simplify the review checkout self-heal back to wipe-and-retry

#9220 fixed a real incident (a corrupt persisted workspace made seven
review jobs fail checkout on the same missing SHAs), but eight review
rounds grew the heal step from ~15 lines into ~60 lines of path-guard
layers (realpath canonicalization, two trailing-slash strip loops, a
denylist case, a RUNNER_WORKSPACE allowlist) plus ~450 lines of tests
pinning their mutation resistance.

Every removed layer defended against a mangled GITHUB_WORKSPACE. That
variable is set by actions/runner; anything that could mangle it — a
compromised runner, a step writing GITHUB_ENV — already executes
arbitrary code on the machine and needs no wipe to do damage, so the
guard cannot defend against the only actor able to trigger it. The
realistic contract is the :? guard: fail loud on a dropped variable.

Kept and still pinned by tests: the pool wipe idiom, the sudo fallback
leg (exact argv), the never-fail exit contract with named survivors,
the identical retry checkout, and the continue-on-error invariants.
Also dropped with the guards: the GNU-only realpath flag and its
host-probe test machinery.

* test(ci): pin the runner-owned GITHUB_WORKSPACE premise before the workspace wipe

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

* test(ci): seal the live override channels into the review wipe step

* fix(ci): refuse a redirected workspace and pin the clean-wipe silence

Addresses the two doudouOUC findings on the simplified heal:

- The wipe now validates the filesystem OBJECT at $WS, not just the
  string: find -P does not descend a symlinked start, so a redirected
  workspace logged 'wiped for a clean retry' while deleting nothing,
  and the secret-bearing review step would then run through the
  redirection. Refuse loud on a symlink or non-directory — POSIX-only,
  no false-positive surface (a legitimate workspace is always a
  runner-created plain directory), and it pins the only sudo-escalated
  wipe in the pool to a validated target.
- The clean-wipe silence branch was unpinned: the reviewer's minimal
  mutant (dropping the if/fi pair) shipped an empty-list survivor
  warning on every heal with the suite green. The clean-wipe test now
  asserts the success annotation and the absence of the survivor
  warning; both mutants verified red.

* test(ci): seal the wipe step's surviving override channels

* test(ci): seal the wipe's surviving override channels, pin its signals

Addresses the open review findings on the simplified heal:

- The seal's premise covered declarative env, $GITHUB_ENV/$GITHUB_PATH
  run writes, and the pre-wipe action set, but three channels passed it
  unchecked: a wipe-step `shell:` or workflow/job `defaults:` wrapper
  re-targets the environment at exec time; SHELLOPTS rides the same
  bash-startup family as BASH_ENV/ENV yet sat outside the dangerous
  name class; and ACTIONS_ALLOW_UNSECURE_COMMANDS re-enables the legacy
  ::set-env:: / ::add-path:: spellings the run-text scan did not match.
  Each channel was reproduced green against the old seal (mutant probe)
  and now turns it red.
- The both-legs-fail test now also pins the else-branch "could not
  wipe" warning, and a dedicated test pins the `[ ! -d ]` refusal for a
  nonexistent workspace — the plain-file test alone still passes a
  guard mutated to `[ -f ]`.
- The non-sudo wipe leg keeps its stderr: the 2>/dev/null discarded
  exactly the diagnostics oncall needs when the wipe fails, and the
  sudo leg already ran unsuppressed.

* fix(ci): refuse workspace wipe through symlinked path components (#9327)

---------

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: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-19 00:56:21 +00:00
Shaojin Wen
846fc05461
feat(ci): post autofix failure-path handoff comments bilingually (#9386)
* docs(autofix): design bilingual failure-path handoff comments

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

* feat(ci): post autofix failure-path handoff comments bilingually

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

* fix(ci): pin bilingual handoff sanitization by content, escape withdraw excerpt

Address review R1-1/R1-2/R1-3:

- Escape + iconv the issue-lane withdraw failure.md excerpt, the one
  publish site without `<!--` escaping: a failure.md quoting an HTML
  comment whose closer sits past the 1500-byte cut opened an
  unterminated comment that swallowed the new 中文说明 block (R1-3).
- Widen the escape-site count test to the multi-`-e` sed form
  (9 -> 12 sites) and pin the full zh sanitization pipeline per-site
  on both lanes; dropping the `<!--` expression from either zh site
  or a tag substitution from the withdraw site now fails (R1-1).
- Pin EN/ZH correspondence for every non-empty assignment site of
  HEADLINE/CAUSE/LAST_FIX/GATE_CLAUSE/IDLE_CLAUSE/REMEDY: count-only
  pins let a swapped adjacent HEADLINE_ZH pair pass all tests (R1-2).

All four mutation witnesses from the review now fail the suite
(verified locally: probe each mutation, expect red, restore).

* fix(ci): close the bilingual handoff review gaps (R2/R3)

Workflow fixes:
- Neutralize :: in the issue-lane run-log dump loop (agent-written
  files on step stdout parse as workflow commands; the PR-lane twin
  already did this) — R2-1.
- Extend the wrapper-defense substitutions (<details, </details,
  <summary) to the three excerpt sites that only escaped <!-- and
  now sit above the new 中文说明 wrapper: API_ERROR_DETAIL (flows into
  HEADLINE_ZH inside the wrapper) — R2-3; the PR-lane DETAIL_FILE
  excerpt (address-summary/no-action files are mandated to END with
  their own <details> tail, so a cut-straddling tail leaves a live
  severed opener) — R2-4; the withdraw failure.md excerpt — R3-1.
- The withdraw comment's 中文说明 block now renders unconditionally
  with a translated REASON (REASON_ZH per branch), mirroring the
  PR-lane headline floor: crash shapes where run-agent.mjs writes
  failure.md itself no longer degrade to zero Chinese — R3-2.

Accepted and documented (design doc §5): fence-token severance across
the byte cut — render-only, markers parse raw, and a balancing
heuristic stays wrong when the cut lands mid-closer — R2-2.

Test pins (each mutation-verified locally): branch-selected zh labels
— R2-5; zh gate-note text + condition + position — R2-6; failure.zh.md
membership in all four dump loops plus the issue-lane :: sed — R2-8;
the ZH_DETAIL guard — R2-9; full-line rm -f pins on the three pre-agent
cleanup sites — R2-10; the BODY append shape — R3-3; wrapper internal
ordering — R3-4. Design doc §2 reconciled with §5 on the no-detail
fallback sentence — R2-7.

* fix(ci): close the R4 review gaps (case-insensitive tag defense, pin gaps)

---------

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-19 00:17:35 +00:00
qwen-code-dev-bot
97544b322d fix(ci): host-probe the macOS lane's bash and GNU dependencies (#9370)
The revived macOS lane ran the shared suite for the first time since the
merge queue went dark and failed on exactly three tests, identical across
three consecutive runs: the bite-check block and the baseline A/B green
path die with `mapfile: command not found` (a bash >= 4.4 builtin; macOS
ships 3.2), and the health-probe repair case trips over BSD `mktemp -u`
attempting to create where GNU's print-only `-u` just names the canary.

Both suites pin scripts that only ever execute on Linux runners —
ubuntu-latest or the Linux ECS pool — so the defects cannot exist in
production; the suites just cannot run those scripts on a macOS host.
Follow the convention #9220's fix established for the realpath case in
the same file: probe the host capability, not the platform, and skipIf
the three dependent tests where it is absent. A Mac with a newer bash or
GNU coreutils fronting PATH keeps the coverage; the Linux lane runs all
three unconditionally. The large bite-check test body is re-indented by
prettier around the new skipIf wrapper; no other token changed.
2026-08-18 21:00:47 +00:00
qwen-code-dev-bot
92eec8fb90
Merge branch 'main' into fix/revive-platform-lanes 2026-08-19 02:09:18 +08:00
Shaojin Wen
f0dcdfc157
feat(triage): add a deterministic flakiness gate to sandboxed verification (#9130)
* feat(triage): add a deterministic flakiness gate to sandboxed verification

Closes #9125. PR #9086's ~50% mtime-assertion flake passed every automated
layer because each executed the changed tests exactly once — a coin flip a
single green run cannot distinguish from health. The gate re-runs the PR's
added/modified unit-test files N times (default 5, vars.QWEN_VERIFY_FLAKE_ROUNDS
to override, clamped to 2..10) through the same entry points CI uses and
compares outcomes per group across rounds.

Design constraints, each pinned by a workflow test:

- One-way authority: 'flaky' demotes the published headline (even a trusted
  agent merge-ready); no gate value can raise or soften one. The gate runs
  the PR's own test code, so it can always be neutered — but a gate that can
  only demote is not worth forging.
- Divergence-only signal: a group failing identically every round is
  deterministic (CI owns it) and an environment-sensitive suite must not
  false-positive here; both report informationally, never demote.
- Fail open: the gate is not under -e and every terminal path exits 0 — a
  gate bug reports verdict 'error' instead of taking down the verify lane.
- Honest file list: recorded from HEAD^1..HEAD before install/build hands
  the workspace (and .git) to PR lifecycle code; the gate consumes the
  root-owned recorded list and never re-derives the diff.
- Untrusted text stays out of outputs: summaries are fixed text plus
  counters; PR-controlled paths live in flake-gate.log, embedded through
  the publisher's escaping emit_block.

Job timeout raised 150 -> 175 for the gate's ~25m worst case (15m round
budget checked before each invocation + one 10m-capped in-flight run).

* fix(triage): survive the runner wrapper's -e, per-file gate granularity, hardened log staging

Round-1 review + sandboxed-verify feedback, all seven findings:

- set +e after set -uo pipefail: the runner wraps every run: block in
  'bash -e -o pipefail' and set -uo does NOT clear that inherited -e, so
  the first failing test invocation killed the step — fail-open inverted
  to fail-closed for exactly the flaky/consistent-fail populations the
  gate classifies (verify cells C/D). An EXIT trap additionally converts
  any abnormal ending (set -u death) into the fixed 'error' verdict.

- Per-FILE groups: one runner invocation per changed test file, so a
  consistently failing file can no longer mask another file's run-to-run
  divergence behind a shared exit bit.

- Owning-package resolution: nearest ancestor package.json (nested
  workspaces like packages/channels/base are entered themselves) plus a
  vitest-config probe; unsupported runner families (packages/desktop's
  bun test) and */e2e/* specs are logged out-of-scope instead of being
  mis-run as permanent consistent-fail noise.

- Operands are ./-prefixed before %q, so a checked-in filename beginning
  with '-' (e.g. --config=x) can never be parsed as a runner option.

- Log staging moved to a dedicated always() root step after the agent
  exits — the last write to verify-results/flake-gate.log — and the
  publisher pins that exact path instead of find|sort|head, so an early
  agent abort cannot lose the matrix and agent-era PR code (which owns a
  chowned verify-results) cannot control or shadow what is embedded.

- Detection math corrected: N=5 catches a 50/50 flake with ~94%
  (1 - 2*(1/2)^5), not ~97% — all-pass and all-fail rounds both miss.

- New behavioral suite executes the extracted gate and publisher
  fragments under the production wrapper itself (bash --noprofile
  --norc -e -o pipefail) with scripted per-file P/F sequences: pass,
  flaky-next-to-consistent-fail, consistent-fail, missing-list error,
  out-of-scope n/a, nested-package + leading-dash operand, and the
  seven-value one-way demotion — closing the structural blindness where
  YAML-string tests stayed green while the shipped behavior regressed.

* fix(triage): isolate flake-gate rounds, classify infra exits, widen runner resolution (#9130)

Review-round fixes for the deterministic flakiness gate:

- Reset shared state between rounds (restore tracked files, tear down
  test-user processes, fresh per-invocation TMPDIR) so a deterministic
  test cannot fail on its own residue and fake a divergence (R1-8).
- Classify timeout/signal exits (124, 128+N) as infrastructure, not F
  marks, and report the informational timeout verdict instead of a fake
  flaky (R2-3/R3-2).
- Resolve the vitest runner by owning package + vitest's real config
  list (vite.config.* included), keyed on the package lookup instead of
  a packages/* prefix, so webui and integrations workspaces are re-run
  instead of skipped (R2-2/R3-3).
- Narrow the scripts/tests arm to the pinned config's *.test.{js,ts}
  include set so admitted-but-rejected files are skipped, not mis-run
  into a bogus consistent-fail (R3-11).
- Harden the gate-log staging: kill leftover build-user processes, and
  remove a planted destination entry before copying so a FIFO/symlink
  can neither hang the copy nor redirect it (R1-5).
- Cap the embedded gate log at 10000 chars to keep the assembled
  comment under GitHub's 65,536-char limit (R3-4).
- Record changed files with core.quotePath=false so non-ASCII test
  filenames are not silently dropped (R3-5).
- Behavioral tests: hermetic timeout/pkill stubs (the suite no longer
  depends on GNU coreutils, fixing the macOS red), infra-exit and
  round-reset scenarios, trap-abort fail-open, node --test arm,
  FLAKE_ROUNDS clamping, fixed-shape summary, record-step shape pins.

* test(triage): follow the widened special-file strip into the vitest twin pins

Commit 13708068bd widened the verify-lane artifact strip from symlinks
only to \( -type l -o -type p -o -type s -o -type b -o -type c \) so a
planted FIFO/socket/device cannot hang or redirect the collection — but
two pins in scripts/tests/qwen-triage-workflow.test.js still asserted
the old '-type l -delete' literal and went red (the Test job's only
failures). Update both pins to the full new expression; the intent they
guard (strip present, and AFTER the artifact copy) is unchanged, and the
tmux-side pin keeps the old literal because the tmux lane still uses it.

* fix(triage): build-user round resets incl. pre-round-1, artifact-loss-proof flaky demotion, mechanism-anchored pins

Round-4 review, all 22 findings (2 Critical dual-anchored + 20):

- Round-state reset (Critical): run the reset AS THE BUILD USER — a root
  checkout restores node-mutated tracked files as root-owned inodes that
  later node rounds cannot write (EACCES divergence) — add 'git clean
  -fd' (no -x) so untracked round residue is dropped, and run one reset
  BEFORE round 1 so lifecycle-script mutations cannot make the first
  sample differ from the rest. Behavioral scenarios: untracked residue,
  pre-gate tree mutation (both PPPPP/pass), each red without its fix.

- Staging step: continue-on-error (evidence-copying must not let the
  publisher discard a recorded verdict as 'infrastructure failure'), and
  the order chain (pkill -> dir guard -> mkdir -> unlink dst -> cp) is
  now pinned by index comparison, not presence-only regexes.

- Publisher: the flaky demotion now fires in the artifact-download-
  failure branch too — FLAKE_VERDICT travels via job outputs and does
  not need the artifact — instead of a neutral 'results unavailable'.

- Pins anchored to mechanisms, not adjacency (R2-P1): exact-line record
  assignment (kills ;/& status swallowing and covers the -c form), word-
  based no-re-derivation, line-anchored 'timeout -k 30 600 runuser'
  invocation (comment-proof, also pins the per-invocation cap), build-
  user reset lines, agent/gate if-equivalence.

- Unpinned guards now pinned (R2-P2/P3): ACTIONS_* credential strip, the
  env -u runner-file isolation, whole-env key set (a future secret in
  the gate env must be an explicit test decision), intake extension set,
  record->gate handoff filename, child-env line (CI/heap/TMPDIR).

- New behavioral scenarios: wall-budget expiry via a scripted date stub
  (both timeout branches, pinning rounds_done placement), space-bearing
  filename through %q as one operand, bilingual one-way demotion (the
  collapsed Chinese summary is the one verdict a zh reader sees).

* fix(triage): NUL-delimited gate intake, zero-collection class, front-loaded matrix, hardened resets

Round-5 review, all 28 findings:

- NUL-delimited record end to end (git diff -z, grep -z, gate read -d ''):
  quotePath=false only stops quoting of bytes >= 0x80 — ASCII specials
  (backslash, tab, quote, control chars) stayed C-quoted and silently
  failed the $-anchored line grep with no skip-log entry. The raw diff
  never passes through $( ) (command substitution strips NUL). Intake
  extension set gains .mts/.cts (vitest's default include collects them).

- Zero-collection class ('N' mark): a file the runner's include set
  rejects exits 1 every round with 'No test files found' — publishing
  that as consistent-fail claimed 'deterministic, CI owns it' with both
  clauses false. All-uncollected lands n/a; mixed runs pass with the
  not-collected count in the summary. Faking the marker can only
  SUPPRESS a demotion the PR could already dodge — one-way authority.

- Per-invocation detail moved behind the matrix/verdict: the publisher
  embeds the FIRST 10,000 chars, and failure tails (8 KB each) pushed
  the promised per-round matrix past the cap in exactly the flaky runs
  the demotion points at. Plus a bilingual fallback note when the gate
  log could not be staged into the artifact.

- reset_round_state kills FIRST (a live daemon re-dirties the tree after
  checkout), with SIGKILL + a bounded wait replacing the one-shot TERM.

- Behavioral hardening: runner-injection-env and operand-resolution
  guards baked into the default stub (pins the cd and %q for every arm),
  hostile filenames through the generic and node --test arms, mid-round
  budget expiry, flaky-outranks-timeout, infra-exit amid divergence,
  vitest.workspace.ts entry, exact summary counters, step-summary
  read-back, gate status line in the demotion drive.

- Structural pins anchored to mechanisms: exact NUL-record statements,
  continuation-proof no-re-derivation (plus log/show/whatchanged),
  unset-before-invocation ordering, FLAKE_ROUNDS wired to the repo var,
  inv_tmp lifecycle, record/gate if-pins, flake-before-agent order,
  staging line-anchored order chain incl. both guard halves and the
  guard's rm reaction, DOWNLOAD_OUTCOME wiring.

* fix(triage): close the desktop-app runnability hole and the staging replant race; demote in every terminal branch

Round-6 review (2 Critical + 19 Suggestion; 17 applied, 2 declined with
rationale on-thread):

- Desktop/docs-site exclusion (Critical): packages/desktop/apps/* each
  carry a package.json plus a BUILD vite.config.ts, so the generic
  resolver treated bun-family tests as runnable — 102/319 real desktop
  test files misclassified, published under a false include-set-mismatch
  diagnosis while draining the shared wall budget. Explicit skip arm
  ahead of the generic arm; behavioral scenario pins both trees.

- Staging replant race (Critical): the one-shot pkill lost to setsid
  daemons/continuous forkers, and verify-results stayed node-owned — a
  survivor could swap the staged log for a symlink between cp and
  upload-artifact's link-following enumeration: root-readable-file
  exfiltration into the public comment. The kill now uses the bounded
  survivor wait, and the directory is chown -R root:root before the
  copy, revoking the replant capability regardless of the race.

- A recorded flaky now demotes in EVERY terminal publisher branch:
  cancelled and job-failure used to post the neutral notice while
  needs.verify.outputs still carried the verdict (the download-failure
  branch already honored it). Bilingual, with run-log pointers.

- Dead 'uncollected' counter removed (ncoll already counts N marks).

- Tests: ghost-file and desktop-skip scenarios; behavioral child-env
  guards in the default stub (CI/heap/TMPDIR-under-RUNNER_TEMP);
  invocation-count ground truth for the mid-round budget stop; sixth
  drive field pins the Chinese gate status line; adjacency-pinned record
  statements; anchored credential-strip/wait-loop/publisher pins;
  cancelled/failure/download branch pins; upload transport pins;
  workflow/job-level env emptiness pins; guard then-body pin.

Declined (reasoning on the threads): whitespace-spelling re-derivation
evasions (bounded by one-way authority — re-derivation can only narrow
the gate), and an inv_tmp position pin (superseded by the stub's
behavioral TMPDIR guard, which every scenario now enforces).

* fix(triage): per-sample reset equivalence, 125-127/N-transition classification, intake+staging hardening

Round-7 Critical cluster on the flakiness gate:

- Reset runs before EVERY invocation (not once per round) so file i never
  samples what files 1..i-1 left this round; HOME joins TMPDIR in the
  per-invocation isolation; `git checkout HEAD -- .` restores from the
  root-pinned commit instead of the index; `git clean -ffd` also drops
  nested-.git dirs plain -fd refuses; both git calls gain the lane's
  runner-injection strip and a `timeout -k 30 120` wrapper, and a failed
  reset fails open to the fixed error verdict instead of sampling dirty.
- Exits 125-127 (timeout's own failure modes) classify as infrastructure
  like 124/128+N; a per-file collection-state transition (N next to P/F)
  is divergence, no longer collapsed to pass/consistent-fail.
- Record step: diff-filter gains T (typechange); a grep error (status 2)
  fails the step loudly instead of starving the gate to n/a; the
  owning-package walk hands its result through a variable, never a `$( )`
  capture that strips trailing newlines.
- Staging: chown preserves modes, so the root re-own is completed by
  `chmod -R go-rwx` and a post-revoke sweep before the copy.

Behavioral scenarios pin each defect (all fail pre-round, measured):
per-sample equivalence across residue/nested-repo/staged/HOME classes,
exit-127 infra classification, and N-transition divergence.

* fix(triage): give the gate a root-only home and rebuild the upload tree

Round-7 R7-8 (Critical) and the R1-5 line it supersedes, one root cause:
$RUNNER_TEMP's top level is uid-1000 mode-755 on this pool and the job
container's `node` is uid 1000, so PR-controlled code held OWNER WRITE
on the directory holding every gate working file — and each root-side
consumer followed symlinks. Four probe-confirmed exfiltration routes:
the staging source, the recorded list (swapped for /proc/<pid>/environ,
whose NUL-framed KEY=VALUE is exactly the intake framing, landing
ACTIONS_RUNTIME_TOKEN in the published log), the fixed-path round
output, and the destination entry itself (renameable even after the
in-place hardening, so a kill-race survivor could swap the whole tree
for a symlink farm that upload-artifact follows).

Removing the capability rather than patching each consumer:

- All gate working files (list, log, detail, round output) now live in
  $RUNNER_TEMP/flake-gate, created 0700 root:root by the record step
  (rm -rf first: the entry may be a plant from an earlier run on the
  persistent pool). A directory node cannot enter is one whose entries
  it can neither create, unlink, nor rename — routes (a)(b)(c) close by
  construction.
- The gate verifies that home fail-closed before reading anything:
  not a symlink, a directory, owned by the EFFECTIVE user (root in
  production; keeps the extracted script runnable under a harness), and
  mode 700 — otherwise the fixed `error` verdict, exit 0.
- Staging BUILDS a trusted upload tree in that home instead of hardening
  the agent-era one: kill+wait unconditionally (node can unlink the log,
  which must not skip the rebuild), then copy only regular files with
  --no-dereference out of verify-results, then the authoritative log
  last. The artifact now uploads from the rebuilt tree, so the entry the
  enumeration walks was never in a PR-writable directory — route (d).

The publisher's paths are unchanged (same inner layout). Behavioral
scenario pins the fail-closed refusal; the staging pin chain follows the
rebuild order.

* fix(ci): close flake-gate TOCTOU, reset-integrity, and verdict-discard routes (#9130)

* fix(ci): keep sub-2-round gate stops informational and scrub startup env channels (#9130)

The reset-failure early stop and a mid-run home swap both reached
classification with fewer than two completed rounds — one published
`pass` off a single agreeing round, the other discarded samples that
already encoded a computed flaky, letting a PR dodge its demotion by
renaming the gate home. Both stops now degrade to the informational
timeout verdict under two rounds while an observed divergence still
demotes, and a swap after samples keeps the collected results.

The record/gate/staging blocks also scrubbed only PATH, while the
channels consumed at shell/loader startup stayed live: BASH_ENV and
the LD_* loader channels are now blanked at step env (with a
fail-closed check if the blank loses), BASH_FUNC_* imports are
dropped by a one-shot env -i re-exec whose child marker is positional
(an env sentinel would be forgeable through the same file-command
channel), and the GIT_* family is stripped on the reset and sampling
children. Staging additionally validates the run identity stamped by
the record step, and a verify-results stat race degrades to a skipped
copy instead of aborting before the authoritative log copy.

The count pin covered five of the seven stripped node-run commands,
which is what failed the scripts lane; the job timeout budget now
includes the resets the deadline check runs ahead of (175 -> 190).

* fix(ci): close gate re-exec startup races and stale-evidence paths (#9130)

* fix(ci): harden flake-gate startup decisions and close staging swap windows (#9130)

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

* fix(ci): move the flake-gate home out of PR-writable space; pin git metadata

Rounds 8-16 kept re-reporting one class (R8-1, R8-36, R12-2 and its
re-reports, R13-21, R15-1, R16-1) because every fix was a re-validation
of a path that could still be swapped underneath it. The premise, not
the consumers, was wrong: rename(2) and unlink(2) need write permission
on the PARENT directory, never on the entry, and $RUNNER_TEMP's top
level is uid-1000 mode-755 on this pool while the container's node is
uid 1000. A 0700 root:root home there could always be renamed away
wholesale and replaced — so each added check (ownership, mode, inode
anchor, run-id marker, opened-directory re-stat) only narrowed the
window, and the inter-step window before a `uses:` upload could not be
closed at all, because that step cannot run shell.

- The home moves to /flake-gate. `/` is root:root 755 in this container:
  node can neither create, unlink nor rename entries in it, so the
  entry — and everything reached through it — is outside PR-controlled
  reach by construction, with no window left to re-check. The upload
  ships that tree directly. No env override: $GITHUB_ENV is uid-1000
  writable, so a configurable home would be a PR-reachable channel, and
  the record step rm -rf's whatever the home names. The extracted-script
  harness relocates that one constant into its scratch tree instead, and
  the production value is pinned structurally.
- inv_tmp stays under RUNNER_TEMP by necessity (the build user cannot
  enter the root-only home) and gains the :? guard.
- .git is re-owned to root after prepare's workspace chown (R4-1/R4-2):
  with it node-owned, a lifecycle script could plant a smudge filter and
  have root's per-invocation reset execute it every round, or rewrite
  HEAD so the restore installs a tree of its choosing. Non-fatal, since
  the reset already runs git with GIT_* stripped, under a timeout,
  against an OID pinned before any PR code ran.
- The scripts/tests arm gets the same substring-collision labelling the
  generic arm already had, so a same-stem sibling collected by vitest's
  positional filter is named in the attribution (R8-31/R13-28 lineage).

Suites: helper 112/112, twin 134/134 — both run as a NON-ROOT user,
which is what CI does; as root the gate pins its own PATH and the
harness stubs are bypassed by design, so a root run reports false
failures.

* fix(ci): clear the publisher's downloaded results before the download

R16-3: publish-verify runs on the persistent ECS pool and downloads the
artifact into a workspace-relative `verify-results`, which the runner
does not clean between jobs. The publisher treats the presence of
`verify-results/flake-gate.log` as proof that THIS run staged it, so a
previous run's log — possibly from another PR — could be embedded as
this run's evidence. The verify side already applies the same rm-first
rule to its own $RUNNER_TEMP tree; this brings the publisher in line.

Pinned by a structural test asserting the clear step exists and precedes
the download.

* fix(ci): drop the .git re-own — it breaks the build user's own reset

Reverting the .git hardening from 387a843434 after measuring it: the
per-invocation reset runs as `node` (root's git trips the
dubious-ownership guard), and a root-owned .git makes it fail at
`Unable to create '.git/index.lock'` — every round's reset would abort,
which the gate correctly reports as `error`, i.e. the gate would stop
working entirely. Probe: root:root + go-w on .git, reset as the build
user → 'Permission denied' on index.lock.

The R4-1/R4-2/R16-1 surface it aimed at (metadata-steered resets) keeps
its existing defences — OID pinned before sampling, GIT_* stripped from
the reset's environment, timeouts, and the per-round strip of .git
execution vectors — and the residual (that strip is a denylist, so
include/includeIf indirection can still reach it) is tracked as
follow-up rather than closed by a change that disables the gate.

* fix(ci): close flake-gate startup-channel shadowing; blank the upload loader env (#9130)

* fix(ci): close flake-gate startup-window races in the re-exec and verdict path (#9130)

R18-1: anchor the record/gate/staging re-exec snapshots to the inode
bash is executing (fd 255) and re-verify the path against it before the
exec — a swap that lands between bash's open of the runner-written step
script and the snapshot is filesystem state the kill cannot un-land.

R18-2: POSIXLY_CORRECT in all four gate-family step envs — bash then
refuses BASH_FUNC imports named after special builtins at startup, so a
poisoned `set` cannot run attacker code on the body's first command
(the in-script POSIX switch arrives one command late); a poisoned
startup fails the step red, and the abort is the refusal.

R18-3: every pre-re-exec refusal writes through /usr/bin/printf — echo
is a regular builtin, shadowable by a BASH_FUNC_echo%% import even in
POSIX mode (probe-verified).

R18-4: parent-side identity gates query the kernel via /usr/bin/id -u
instead of reading $EUID, which bash imports from the process
environment — one planted EUID line could skip the kill sweeps and the
poisoned-env refusals in every later step.

R16-4 (interim): the publisher's full-report branch maps a missing or
unrecognized FLAKE_VERDICT to a visible fixed-text error line instead
of silently dropping it — on that branch the gate ran and owes a
verdict, so absence means the uid-1000-writable $GITHUB_OUTPUT backing
channel corrupted it in transit. Fixed text only: the raw value is
attacker-influenced on this path and is never embedded.

The behavioral harness now applies the step env block (production
parity), and the two round-15 poison scenarios are re-pinned to the
stronger startup refusal.

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-18 15:26:24 +00:00
Shaojin Wen
66ec0237db
Merge branch 'main' into fix/revive-platform-lanes 2026-08-18 22:35:43 +08:00
易良
2785480685
fix(devx): fail with actionable message when unit-test build prerequisites are missing (#9149) (#9171)
* fix(devx): fail with actionable message when unit-test build prerequisites are missing (#9149)

Package-local unit tests in packages/cli import workspace packages
(acp-bridge, sdk, web-templates, channels/*) through their built dist/
output plus the generated git-commit.ts. In a fresh worktree that shares
the main checkout's node_modules, or after a deep clean, those artifacts
do not exist and vitest fails during collection with resolution errors
that blame the wrong thing.

Add a vitest globalSetup guard that checks the prerequisites up front and
exits with a message naming every missing piece and the command that fixes
it (npm run build). Document the prerequisite in the AGENTS.md unit-testing
section.

* fix(devx): address R1 review findings on the unit-test prerequisite guard

- normalize win32 path separators so the guard works on Windows (R1-1)
- use Copyright 2026 Qwen Team header (R1-2)
- drop over-included packages/sdk-typescript; every sdk import in the cli
  test graph is an aliased /daemon* subpath (R1-3)
- add a sync assertion test: every builtin channel dynamically imported by
  channel-registry.ts must stay listed in DIST_PREREQUISITES (R1-4)
- cover the vitest-invoked entry point via exported checkAndReport (R1-6)
- mirror real manifest shapes (exports.default / import variants) in the
  test fixtures (R1-7)
- report missing/unreadable package manifests through the normal exit path
  instead of crashing with a raw stack trace (R1-8)
- derive the package key from vitest-s resolved project root, so
  vitest run --root packages/cli from elsewhere is covered (R1-9)
- probe every exports entry targeting dist/, not only the . entry, so
  missing unaliased subpath builds are reported too (R2-1)

* fix(devx): extend the unit-test prerequisite guard to packages/core

Issue #9149's scope names packages/cli AND packages/core, but the guard
only covered cli: eight core test files (providers/__tests__/presets/*,
provider-config.test.ts) import the bare '@qwen-code/qwen-code-core'
specifier, which resolves through the package's own exports to
dist/index.js — on a fresh checkout 'cd packages/core && npx vitest run
src/path/to/file.test.ts' (the AGENTS.md-documented command) still died
with the opaque 'Failed to resolve entry for package' error.

- Add 'packages/core': ['packages/core'] to DIST_PREREQUISITES
- Wire the same globalSetup guard into packages/core/vitest.config.ts
- Skip wildcard pattern exports entries ('./dist/*') in distEntryFiles:
  core's manifest carries them and they name no individual file —
  probing them literally would block core test runs even fully built
- Generalize the fixture builder to every DIST/GENERATED_PREREQUISITES
  entry, add coverage for the core dist requirement and the wildcard
  skip, and move the 'no known prerequisites' example off packages/core
- Note the core self-import in AGENTS.md

Probe-verified both arms at this commit: dist moved aside -> the guard
prints the actionable message and stops the run; dist restored -> the
test file passes (11/11).

* fix(devx): harden the prerequisite probe and its drift tests

- Probe manifest 'main' entries spelled without a leading './': all
  guarded manifests use "main": "dist/index.js", which the old
  startsWith('./dist/') predicate never matched, so the documented main
  probe silently collected nothing. Normalize before the prefix check.
- Tolerate digits in builtin channel names in the sync test's registry
  regex ('channel-[a-z0-9-]+'), or a future channel with a digit in its
  npm name escapes the drift check.
- Add the reverse sync assertion: every listed packages/channels/*
  prerequisite must map back to a channel-registry import (channel-base
  excepted as the channels' build dependency), so removing a builtin
  channel cannot leave a stale entry that hard-blocks cli test runs
  with a misleading 'fresh checkout' message.
- Cover the main-entry probe with a fixture test.

* fix(devx): fail loud on stale probes, align key derivation under symlinks

Round-3 review findings on the prerequisite guard:

- R3-1: a listed package whose manifest enumerates zero ./dist/ targets
  (require-only or nested-condition entries) passed the probe silently —
  report 'exposes no dist/ entry files to check (guard probe may be
  stale)' instead, so a stale probe cannot resurrect the raw resolution
  error this guard exists to replace.
- R3-3: when SOME dist entry files exist, the missing one is no longer
  diagnosed as 'has not been built' + a plain npm-run-build prescription
  (a successful build can legitimately leave a stale exports entry); the
  message now says the build output is incomplete or exports points at a
  file the build does not emit, and to check the package's exports
  entries when rebuilding does not help.
- Key derivation now realpaths both cwd and root (with a fallback to the
  raw path): repoRoot descends from import.meta.url, which Node resolves
  through symlinks, while vitest resolves root with a plain path.resolve
  — comparing them raw let a symlinked ancestor silently disable the
  guard.
- R3-2: the channel drift-check character class now tolerates digits,
  underscores and dots per npm naming rules.
- R3-4: renamed the test that claimed win32-separator coverage it never
  exercised; its body is the degenerate repo-root silent-yield path and
  the comment now says so.
- R1-6 (partial): added default-export coverage — project.config.root
  extraction and the process.cwd() fallback, asserting no exit in a
  built repo. The exit-1 arm of the default export stays untested: it
  needs a root-injectable seam the entry point deliberately does not
  have; checkAndReport's return-1 and message remain covered directly.

Tests: 21/21; mutation probes confirm the zero-enumeration and symlink
tests catch their regressions.

* fix(devx): hermetic guard tests, alias-aware probe, explicit gitlab build

- drive the default-export tests against a hermetic fixture checkout via
  QWEN_VITEST_GUARD_ROOT (in-process and subprocess), so they hold on an
  unbuilt worktree instead of depending on the real repository state
- skip dist targets the consumer aliases to TypeScript source when probing,
  so a missing-but-aliased dist file no longer blocks runs that would pass
- make the remedy message context-aware: the git-commit hint appears only
  when a generated file is missing, and a stale-probe line gets its own note
- add packages/channels/gitlab to buildOrder: it is a cli channel-registry
  builtin like its siblings and used to build only transitively
- add drift tests pinning the globalSetup wiring in both vitest configs

* fix(devx): ignore commented vitest aliases

* fix(devx): anchor the vitest globalSetup guard to the config file

R1-1: a relative globalSetup path is resolved against vitest's root (the
process cwd without --root), not the config file's directory, so the
prerequisite guard only loaded when vitest happened to run from inside the
package. `npx vitest run --config packages/<pkg>/vitest.config.ts` from the
repository root died with "Cannot find module .../vitest-global-setup.js"
before any test — the cause-hiding failure class this guard replaces.
Resolve it with path.resolve(__dirname, ...) in both packages/cli and
packages/core configs, and update the wiring-sync assertion (now robust to
prettier line-wrapping; flip-verified red when reverted to a bare string).

---------

Co-authored-by: yiliang114 <yiliang114@users.noreply.github.com>
2026-08-18 13:19:09 +00:00
qwen-code-dev-bot
c1a7a681a1 fix(ci): close review round on the revived platform lanes (#9370) 2026-08-18 12:41:28 +00:00
qwen-code-dev-bot
8147795346
Merge branch 'main' into fix/revive-platform-lanes 2026-08-18 15:32:41 +08:00
易良
19a4b973eb
fix(ci): back-port the checkout-heal wipe guard to the triage and serve-ab wipes (#9277)
* fix(ci): back-port the checkout-heal wipe guard to the triage and serve-ab wipes

The "empty the workspace, keep the directory" idiom exists in three
copies; only the review workflow's copy received the #9220 hardening
(canonicalization, trailing-slash strip, RUNNER_WORKSPACE allowlist).
Measured on main for #9265, the two triage guards let non-canonical
spellings of the guarded roots through (/home/, /home/., //usr,
/root/, /var/ all reached the rm), and serve-ab's wipe had no guard
at all — even `/home` or an empty string arrived at `find … -exec
rm -rf`.

Port the reference guard to all three sites, keeping each site's
exit contract: triage fails loud both before and after external
code, serve-ab stays bare under the job's `-eo pipefail` so an
unclearable workspace fails before either checkout builds on top of
the leftovers.

Pin each ported copy with its own tests: bad-path batteries under an
rm recorder (the destructive primitive cannot fire under any edit),
an allowlist-escaping `..` case gated on a GNU-realpath host probe
(the lesson from 90fa6bb4), a realpath-absent trailing-slash
RUNNER_WORKSPACE case, and text pins on the ported layers. Every pin
was mutation-verified red against a deletion of the layer it guards.

* test(ci): pin guarded serve wipe

* fix(ci): close wipe guard fallback gaps

* fix(ci): fail closed without realpath

* fix(ci): keep wipe guards portable

* test(ci): pin wipe-guard RWS layers and unmask the pre-run battery

- run the rewritten pre-run sweep battery under -e -o pipefail so a
  failing sweep can no longer report success (bare bash -c masked it)
- pin the RWS '..' refusal and degenerate-root refusal text in all
  copies, and add RUNNER_WORKSPACE='/' exec cases to both copy suites
- exercise both pre-run and post-run copies in the realpath-absent
  refusal test
- replace the '..' escape vector with a symlink escape that only the
  realpath line can refuse, and correct the mutant-outcome comments
- add the serve-ab wipe-before-checkouts ordering pin from the sister
  suite and a happy-path RWS canonicalization pin

* test(ci): correct wipe-guard mutant-outcome comments for find -P

The symlink-escape comments claimed that with the WS realpath line deleted, find reaches rm through the link target. GNU find's default -P mode does not descend symlink operands: the mutant passes every guard, wipes nothing, and exits 0, so only the non-zero-status assertion catches it — the rm-log assertion passes vacuously. Reword both twin comments (R5-1).

---------

Co-authored-by: yiliang114 <yiliang114@users.noreply.github.com>
2026-08-18 06:49:40 +00:00
qwen-code-dev-bot
a119a731d0
Merge branch 'main' into fix/revive-platform-lanes 2026-08-18 13:14:39 +08:00
Shaojin Wen
a4a3850fe5
fix(ci): make autofix busy detection fail closed and mark dispatched PRs (#9329)
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
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
Security Checks / Dependency CVE audit (push) Waiting to run
Security Checks / Secret scan (TruffleHog) (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (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 / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
* fix(ci): drop pull_request_review events on closed PRs at the route gate

Reviews on merged/closed PRs have nothing to address, yet each one started an autofix run that spun up a runner only to exit no-op. Observed 2026-08-16: 24+ finding-reply reviews on merged #9222 and 26 runs on merged #9189 within minutes (issue #9296). Add a PR open-state clause to the route prefilter; the scheduled scan remains the backstop, and address-time revalidation already drops targets whose PR closed after dispatch.

* fix(ci): make autofix busy detection fail closed and mark dispatched PRs

Silent API failures in the busy-PR enumeration re-dispatched PRs whose address legs were already running or queued (issue #9296): each duplicate burned one build-cli (~5 min) before cancelling a queued sibling leg through the per-PR group's latest-wins queue.

- Any enumeration failure (run list or per-run jobs view) now empties the scan's candidate set for this pass; a forced dispatch keeps its explicit-override semantics.
- Stamp a pending commit-status marker (qwen-autofix/dispatch-pending) on the PR head at dispatch and treat it as busy while fresher than 30 minutes; the address leg re-stamps it success on checkout. This covers the scan->build-cli window where the matrix leg does not exist in the live-run jobs view yet. Commit statuses only: the check-run creation API needs a GitHub App, and the workflow authenticates with a PAT.

Refs #9296

* fix(ci): keep the dispatch-pending marker from blocking past its TTL

Exempt the marker's status context from the HAS_PENDING_CHECKS gate (a
stranded marker otherwise blocked the PR for up to ~330 minutes, not the
documented 30-minute TTL), release it on the address-time discard path,
guard every status write same-repo and dry-run, narrow the fail-closed
carve-out to explicit workflow_dispatch dispatches, emit enum_failed so
an emptied candidate set cannot flip the scheduled issue phase on, and
carry the enumeration error tail in the fail-closed warning. Pin all of
it behaviorally in the workflow contract tests.
2026-08-18 04:59:03 +00:00
Shaojin Wen
72ae65d78f
fix(ci): route the autofix convergence-brake handoff through failure.md (#9371)
The growth brake tells the address-review agent to stop with a handoff
but never names the output file, so round 14 on #9184 wrote handoff.md
— a file owned by run-agent.mjs that the verdict gate does not accept.
The round was reported as missing its required outputs even though the
defer-to-human was correct (run 32076785809). Name failure.md (the stop
file run-agent.mjs wraps into the handoff comment) as the handoff
target, forbid agent-written handoff.md, and pin the directive in the
workflow contract test.
2026-08-18 04:03:21 +00:00
Shaojin Wen
d9d210eb7a
feat(autofix): seed the takeover round counter with /takeover from N (#9321)
* feat(autofix): seed the takeover round counter with `/takeover from N`

Taking over a PR that has already been through several review rounds
restarted the Critical-only brake from zero: the round counter is
window-scoped, and engaging takeover opens a fresh window, so a PR that
spent nine human rounds getting to "almost mergeable" got five more
suggestion-capable rounds the moment it was managed — the diff grew on
nice-to-haves exactly where it should have been converging.

`@qwen-code /takeover from N` now seeds the window's counter at N, so
CRITICAL_ONLY_AFTER_ROUND is reached in the remainder rather than a full
fresh five. This is the one parameterized command form: the literal
prefix must still match TAKEOVER_COMMAND byte-for-byte, the tail is a
bounded 1-2 digit integer, and the captured value reaches nothing but an
integer comparison. Everything else — a prefixed body, a `stop from N`
hybrid, a substitution payload — still fails closed.

The seed rides as its own `<!-- autofix-round-start N -->` marker on a
separate line of the engage ack, never as a field inside
`<!-- takeover-ack engaged -->`. That literal is matched with jq
contains(), closing `-->` included, at seven read sites — four here and
three in the fleet shepherd's paused/resume detector — so an inline
field would silently break all of them: the window key would fall back
to an older ack and the shepherd would age out a PR that was just
re-armed. Same shape as the existing autofix-redcheck marker.

Both round readers fall back to the seed instead of a hardcoded 0, read
it by created_at equality against the window key (so a superseded
window's seed cannot leak forward), and clamp it strictly below the
effective cap so a seed can never park a PR at its round cap on the very
round it is taken over. The seed is window-scoped like every other
census: `/retry` or a bare re-takeover returns the counter to 0.

Both engage acks and the Critical-only audit record now name the seed
when there is one — otherwise the ack reports "round 4/100" on its first
managed round, and the audit record claims five completed rounds on a PR
the loop has run twice.

The growth brake is deliberately not seeded: its baseline anchors at the
window's first measured round, and a pre-takeover baseline is not
recoverable, so growth stays measured from engagement.

* fix(autofix): address the R1 review findings on the takeover round seed

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

* fix(autofix): address the R2 review findings on the takeover round seed

* fix(autofix): address the R3 review findings on the takeover round seed

---------

Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-08-18 01:53:07 +00:00
wenshao
49ae2b2a4b fix(ci): gate the Windows lane's checkout verification per trigger
The first thing the revived triggers hit was not a test failure but the
lane's own plumbing. `test_windows` verifies its checkout with
`verify-checkout-head`, and the input was written when this lane ran in
the merge queue alone: `expected_sha: github.event.merge_group.head_sha`,
with no event gate. On a pull request that expression is empty, the
action refuses an empty SHA, and the lane went red in 63 seconds without
running a test — the first Windows run in six weeks, failing on the
trigger rather than on the code.

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

Pinned generally rather than by name: for both lanes, any step whose
inputs read a `github.event.<event>` context must be gated to that
event, in the step's own `if` or in the expression itself. Restoring the
old spelling turns that test red.
2026-08-18 08:14:42 +08:00
wenshao
7c9c073502 fix(ci): give the macOS and Windows lanes a trigger again
Both lanes are gated on `merge_group`, and no merge queue is enabled on
this repository — the `main` ruleset carries only deletion,
non-fast-forward and pull_request rules, no status check is required,
and merges land as squashes. The last `merge_group` run of anything was
2026-07-02. So the gate was an off switch: the lanes reported as
"skipped" on every pull request, which reads as agreement, and nothing
ever reached them afterwards. The only signal this repository has about
a host that is not Linux with a GNU userland had been silently off for
six weeks, which is how #9220 shipped a GNU-only `realpath -m` in a
workflow guard with the suite that pinned it red on every Mac.

Three triggers now, in cost order.

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

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

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

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

Twelve tests pin the wiring — the triggers, the fail-safe direction of
the gate, the base-commit checkout, that a nightly stays two jobs, and
the alerting — and ten more pin the classifier itself, including the
substring traps (`Shellfish.tsx`, `cryptic.ts`, `plateauDetector.ts`
must not drag both lanes in) and every fail-safe path. Mutation-checked:
flipping the gate to `== 'true'`, dropping the schedule from a lane,
dropping the nightly guard from the ubuntu job, pointing the checkout at
the head, dropping the classifier's test from the helper list, and
removing the schedule trigger are each caught.
2026-08-18 08:09:57 +08:00
Shaojin Wen
90c166585a
feat(release): user-facing bilingual digest for release notes (#9216)
* feat(release): user-facing bilingual digest for release notes

Stable release notes read as a type-bucketed PR list, which users find
hard to scan. The finalize step now asks the model to group changes into
user-facing themes with short intros, mirrors highlights and themes into
a Chinese digest, attaches screenshots found in merged PR bodies (host
allowlist, per-release cap), and collapses the full PR list into an
appendix with normalized titles. Every model failure path keeps today's
v1 output byte-for-byte, and CHANGELOG.md accepts the new v2 marker.

* fix(release): tighten v2 digest fallbacks and changelog skeleton (#9216)

Address review round 1 findings:

- usedAi only counts themes that carry content, so a release whose
  digest has zero model text is no longer reported as AI-generated
- hasChinese is derived from what the Chinese block actually renders,
  not raw model output, so zh-only-on-breaking releases no longer emit
  an empty or English-only section
- a PR repeated inside one theme is deduped instead of discarding the
  whole themes digest with a misleading cross-theme error
- fallback titles in the v2 digest are normalized like the appendix,
  killing the mixed-style look in the degradation case
- normalizeAppendixTitle strips only the conventional types the
  changelog's formatEntry strips, keeping ci/test/security prefixes
- the changelog unwraps the v2 appendix at the same sibling rank as
  v1's Complete Change List instead of nesting it under the previous
  section
- drop a dead summaries max_tokens scaling term and a verbatim copy of
  renderChangeLine's attribution rendering

* fix(release): close digest image breakout and tighten fallback signals (#9216)

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

* fix(release): drop camo proxy and harden digest text validation (#9216)

* fix(release): neutralize markdown breakouts in digest text and images (#9216)

* fix(release): close classification, image-URL, and text-validation bypasses (#9216)

---------

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: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
2026-08-17 00:12:04 +00:00
易良
f9e6b67aa7
fix(ci): stop triaging the autofix bot's own deferred-finding tracking issues (#9264) (#9271)
* fix(ci): stop triaging the autofix bot's own deferred-finding tracking issues (#9264)

Every PR that defers findings for the first time opens a tracking issue upserted by the autofix bot, and the issues trigger (opened/edited/reopened) ran a full triage agent on that bookkeeping issue per deferral — the authorize gate exempts the issues path as read-only, so nothing stopped it. Condition the triage job's issues clause on the creator not being the autofix bot (the same vars.AUTOFIX_BOT_LOGIN || 'qwen-code-dev-bot' identity qwen-autofix.yml upserts under), and route bot-created issues runs to a per-run concurrency group: GitHub evaluates concurrency before the job if, so a run left in the shared per-number group would still cancel an in-progress triage of the same issue before its own skip is evaluated. Pins: the issues-clause guard, the group routing, and the cross-workflow identity sync, all on the parsed document.

* test(ci): pin triage issue guard connectors

* test(ci): harden triage bot guard pins

---------

Co-authored-by: yiliang114 <yiliang114@users.noreply.github.com>
2026-08-16 17:09:42 +00:00
qwen-code-dev-bot
f7f78fab4a
fix(ci): force-push release branch so retries replace failed attempts (#9076) (#9082)
* fix(ci): force-push release branch so retries replace failed attempts (#9076)

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

* fix(ci): re-validate the release version before force-pushing (#9076)

prepare's doesVersionExist check runs minutes to hours before publish
pushes (validation jobs and the production-release approval gate sit in
between), and --force removed the non-fast-forward rejection that used
to serialize the push itself. Concurrent same-version runs could
therefore diverge the npm artifacts, the git tag, and main. Serialize
publish per release tag and re-validate the unshipped invariant — every
published package, the tag, and the release — immediately before the
push; pin all three invariants in the workflow tests.

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

* fix(ci): unify the push-time release guard and key concurrency by dry-run (#9076)

* fix(ci): fail closed on push-time release probes and test the CLI seam (#9076)

* fix(ci): clarify push-time release refusals and keep benign ones out of autofix (#9076)

The push-time guard refused retries after a partial npm publish without
saying where the version had shipped or how to recover, and every refusal
failed the publish job into notify_failure, filing a "Release Failed" issue
and dispatching the autofix agent against releases that did not fail.

- Scan all published packages in strict mode and name every shipped
  location in the refusal (npm packages, origin tag, GitHub release) with
  partial-publish recovery guidance; a decisive hit ends the check so a
  flaky later probe cannot mask the refusal with a probe error.
- Give the guard distinct exit codes: 3 = already shipped (decisive,
  benign), 2 = probe or usage failure. Exit 1 is reserved for uncaught
  node errors so a crash can never masquerade as the benign marker. The
  push step marks exit-3 refusals via the version_refusal job output, and
  notify_failure skips its issue + autofix dispatch for exactly that
  failure while genuine failures still notify.
- Cover runCli's default dispatch (prepare's path), the exit-code
  contract, and the process.exit wiring end to end.

* fix(ci): fail closed when the release ref predates the push-time guard (#9076)

* fix(ci): keep refusals decisive after shipped hits and skip the POSIX-only test on win32 (#9076)

* fix(ci): write push-time guard error annotations to stdout (#9076)

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-Coder <qwen-coder@alibabacloud.com>
2026-08-16 16:33:14 +00:00