mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-25 00:26:31 +00:00
344 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
65c2bb01c0
|
fix(ci): narrow serve-ab's self-hosted wipe to the A/B checkout dirs (#9228)
* fix(ci): narrow serve-ab's self-hosted wipe to the A/B checkout dirs 'Wipe stale workspace before checkout' deleted the whole shared workspace including the root .git, forcing the next job on that runner (e.g. a fetch-depth: 0 review job) to re-download the full ~900 MB of history from github.com. On the ECS pool's slow link that stalls checkouts for 20+ minutes and the fetches drop mid-pack often enough to read as hung runners (2026-08-15: 20 orphaned tmp_pack files, ~6 GB, across 10 runners; one checkout re-downloaded 890 MB in 19m45s). serve-ab only builds inside its own head/ and base/ checkouts and never reads the workspace root, so removing just those two dirs keeps the anti-bleed guarantee without destroying the shared object store. The ci-runner-routing pin now asserts the narrow scope and fails on a whole-workspace wipe regression. * test(ci): derive serve-ab wipe pin from the checkout paths (#9228) Address review suggestions: the wipe targets are now derived from the actions/checkout steps, and the wipe must be exactly one executed (non-comment) rm line covering exactly those paths. Renamed checkout paths, appended whole-workspace wipes, and commented-out or echo'd rms now all fail the suite, while the reverted find-form still does. * test(ci): pin the serve-ab wipe's full executed script and step order (#9228) * fix(ci): match serve-ab's wipe step name and pin to its narrowed scope (#9228) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(ci): exclude the shared root .git from serve-ab's self-hosted wipe (#9228) Replace the head/base-only rm with the exclusion-based find suggested by the R5-3 review: everything is wiped except the shared root .git, which closes the recurring materializer-coverage class (R4-4/R5-1/R5-2/R5-3) at the layer that owns it — anything landed outside .git is removed by the next run's wipe, no matter how it was materialized. This removes a strict subset of what main's whole-workspace wipe removes, so it cannot regress base behavior, while keeping the .git whose destruction forced ~900 MB re-fetches on the ECS pool's slow link. The pin now also locks the execution chain (shell wrapper, no continue-on-error, no BASH_ENV at step/job/workflow level) per R5-4, and the obsolete clone-coverage scan is deleted. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(ci): keep only a real .git and scrub its exec state in serve-ab wipe (#9228) * fix(ci): follow a symlinked workspace root in the serve-ab wipe (#9228) * fix(ci): defang the worktreeConfig bypass and anchor the serve-ab wipe scrub (#9228) The kept-.git wipe tail had two measured holes: extensions.worktreeConfig activates .git/config.worktree, a second local file that git config --local neither lists nor unsets, so a planted core.hooksPath survived the allowlist sweep; and after the heal unlinks a symlinked workspace root the step's CWD still is the link's target, so the CWD-discovered scrub wrote outside the workspace. Add qwen-triage's hardened defang pair (delete config.worktree, unset the extension) and anchor every git call to $WS/.git. Both findings reproduced locally before the fix; both suites pin the new lines and are mutation-tested. --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
37cedea5b2
|
feat(computer-use): replace built-in tools with bundled skill (#9856) | ||
|
|
b4a54a9f05
|
fix(ci): move undeletable workspace residue aside instead of leaving it (#9868)
A leftover the pre-checkout sweep cannot delete does not just fail the job that finds it — it poisons the checkout of every later job scheduled onto that runner. Measured on run 32621267802: residue from a review probe (`.qwen/tmp/review-pr-9748-scratch-verify-…/probe-ws/.qwen/tmp/review-pr-666`) survived on one shared-pool member, and two unrelated PRs then died at Checkout with the same EACCES on the same path. The sweep's own steps had reported success: their ladder ends at a warning, so a workspace it cannot repair is handed to actions/checkout unchanged. The recovery the ladder was missing is a rename. Unlinking an entry needs write permission on the directory holding it — exactly what foreign-owned residue denies on a pool member without passwordless sudo — while renaming needs it only on the two parents, and the workspace root is always the runner's own. So a tree that defeats rm, chmod, and chown still moves out of the way, and the checkout finds nothing to trip on. The destination sits next to the workspace so the rename cannot cross a filesystem and degrade into copy-then-unlink, and the warning names it, because the tree still needs a human. Reproduced in a Linux container with real foreign ownership (residue owned by root, workspace by the runner user, sudo unavailable): before, the step warns `leaked .qwen`, the tree stays, and a checkout-style wipe fails with `Permission denied`; after, the tree is quarantined, the wipe succeeds, and the warning points at where it went. |
||
|
|
a60cbbc54a
|
refactor(core): make utils/ a leaf layer (#9778)
* refactor(core): make utils/ a leaf layer Eliminate every runtime (value) upward import from packages/core/src/utils production modules so utils/ can become a leaf layer with no runtime dependency on the rest of core. Two mechanisms, no behavior change: - Relocate domain-coupled modules out of utils/ into their owning module (agents, config, core, memory, services, tools), and move generic constants/types that live elsewhere into utils/. All `git mv` moves keep history; every import that pointed at a moved file is rewritten. - Extract the remaining value imports as small leaf modules inside utils/ (AuthType, isTool, ToolErrorType, DEFAULT_QWEN_MODEL) and re-export them from their original owners so cross-package consumers are unaffected. doesToolInvocationMatch moves into shell-utils, its only production consumer. Only type-only imports now cross the utils/ boundary. The two deferred inversions in debugLogger (Storage, getTraceContext) are stateful and left for a follow-up. * chore(core): enforce utils/ leaf layer with lint rule Add architecture/no-core-utils-upward-import, which flags runtime (value) imports that leave packages/core/src/utils. Type-only imports, sibling utils imports, and external package specifiers stay allowed; the two deferred debugLogger inversions (config/storage, telemetry/trace-context) are carried on an explicit allowlist. Enable the rule as an error on core sources and cover it with Linter-based tests. * fix(core): restore iconv-lite tree-shaking for sync-file-encoding The utils leaf-layer refactor moved sync-file-encoding from utils/ to services/, but the esbuild tree-shake plugin still matched the old ./utils/ specifier, so its sideEffects:false marker no longer applied and the ACP startup closure regained a static iconv-lite import. Point the onResolve filter at the new ./services/ path. * fix(ci): catch stale integration imports earlier * fix(core): close utils boundary review gaps * fix(core): close self-reference boundary gaps * ci: re-trigger after self-hosted runner checkout EACCES |
||
|
|
a2e458deef
|
feat(auth): add Kimi (Moonshot AI) as a built-in third-party provider (#9814)
Adds a Moonshot preset to the /auth Third-party Providers menu, offering the international and China API endpoints and seeding the current Kimi model catalog. Moonshot speaks the OpenAI protocol, so this is a declarative preset with no new mechanism and no change to the provider type. Model metadata follows Moonshot's published capabilities. K3 is marked thinking-mandatory: its API exposes a reasoning-effort knob but no way to turn thinking off, so a disable shape must never reach the wire. The two code models and K2.6 keep thinking toggleable, and all four accept image and video input, which the K2.6 guide states explicitly. Registers the new credential env key everywhere a provider key has to appear: the no-AK CI gate and its pinned assertion list, and the telemetry provider mapping, both by env key and by request hostname so Kimi traffic is attributed rather than reported as unknown. The three first-run docs that enumerate built-in providers are brought back into agreement, which also picks up entries that were already stale. Closes #9197 |
||
|
|
747dbf00c2
|
feat(web-shell): compact agent activity summaries (#9657)
* feat(web-shell): compact agent activity summaries * fix(web-shell): preserve merged todo timing * fix(web-shell): address compact summary review * test(web-shell): cover compact review edge cases * fix(web-shell): make the folded thought header row fully clickable * fix(web-shell): guard folded thought toggle against popover clicks (#9657) * test(web-shell): cover thinking header hit area and parallel-agent thoughts (#9657) --------- Co-authored-by: 钉萁 <dingqi.jww@alibaba-inc.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
56db17bd4c
|
refactor(cli): enforce utils leaf-layer dependency direction (#9146) (#9737)
* refactor(cli): enforce utils leaf-layer dependency direction (#9146) Move domain-coupled modules out of packages/cli/src/utils into the directories that own them: config/ (dialogScopeUtils, settingsUtils), i18n/ (languageUtils), ui/ (handleAutoUpdate, standalone-update, systemInfo, systemInfoFields, update-relaunch, commands, doctorChecks), nonInteractive/ (nonInteractiveHelpers, chat-recording-failure, tool-result-boundary-diagnostics, permission-suggestions), serve/ (sandbox), services/housekeeping/ (scheduler, non-interactive-scheduler), and commands/review/ (findings). Extract the generic normalizePartList helper into utils/normalize-part-list.ts so utils consumers keep importing downward, and move the MergeStrategy enum into utils/deepMerge.ts (its owner). Add an eslint architecture rule (no-utils-upward-import) that forbids value imports from utils/ back up into a domain directory. Type-only imports stay exempt: they are erased at compile time and cannot create a runtime cycle (Settings in modelConfigUtils, CommandContext in sessionPaths). No behavior change: typecheck, build, and the affected unit tests pass. * fix: use Qwen Team 2026 license header on new files (#9146) * chore: refresh stale utils/ path references after leaf-layer move (#9146) * docs: reconcile no-utils-upward-import header with the allowed type-only set (#9146) * fix(cli): allowlist sandbox process.env accesses after leaf-layer move (#9146) * chore(ci): re-record qwen-autofix.yml size baseline after #9677 (#9146) #9677 recorded qwen-autofix.yml at 392111 bytes while the file it committed was already 397656, so every PR that merged main after it tripped the growth ratchet. Re-record the actual size; the file itself is unchanged by this PR. * fix(review): drop the stale utils/findings.ts digest root after the leaf-layer move (#9146) The #9146 move returned findings.ts to commands/review/, but the digest root lists merged from main still pinned it under utils/, where the file no longer exists — the absent root darkened every review's staleness check and failed review-source-digest.test.ts. Drop the stale file-shaped root from both digest copies and their pins; the commands/review/ directory root covers the validator at its new home, and the two utils helpers keep their file-shaped roots. * fix(review): colocate seatbelt profiles with the sandbox module (#9146) * fix(review): exempt inline type-only specifiers from the utils upward-import rule (#9146) * fix(review): report upward inline type-specifier imports under verbatimModuleSyntax (#9146) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(review): pin mixed-specifier and zero-specifier upward imports in the utils rule (#9146) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(review): anchor the nested-checkout utils rule fixture on the last marker (#9146) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(review): pin that the utils/findings.ts digest root stays removed (#9146) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(review): reword stale-bundle SCOPE header to the post-move helper shape (#9146) * test(review): drop the pre-move utils/findings.ts from the skill-parity fixture (#9146) * test(serve): derive the seatbelt colocation tripwire from BUILTIN_SEATBELT_PROFILES (#9146) * fix(architecture): fail closed on computed dynamic imports in the utils leaf rule (#9146) * fix(cli): point settings.test.ts at the post-move settingsUtils path (#9146) main updated settings.test.ts after this branch moved settingsUtils.ts from utils/ into config/, and the merge kept main's old import specifier, which vite fails to resolve. Repoint it at ./settingsUtils.js; every other consumer already uses the new path. * fix(cli): close utils boundary review gaps * test(cli): cover utils boundary allow paths --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
57285a94f1
|
fix(review): repair permissions before giving up on worktree cleanup (#9748)
* fix(review): repair permissions before giving up on worktree cleanup The review job's end-of-job sweep gave up on the first EACCES and left foreign-owned leftovers in the shared runner workspace; the next review's checkout then died on them (run 32577821716, PR #9718: a scratch-verify tree whose contents this job's user could not unlink, on a pool member without passwordless sudo). Give the removal a repair ladder — chmod what this user owns, then passwordless sudo chown/chmod where the pool member has it, each followed by a retry — and refuse the ladder on paths that resolve through symlinks, since its sudo leg escalates to root. Members without sudo still degrade to a named warning: nothing unprivileged can remove a foreign-owned tree, but the sweep must never fail the job. Pin the ladder in the cleanup contract test so a rewrite cannot silently drop it back to warn-and-leave. * fix(ci): record qwen-code-pr-review.yml's shipped size in the workflow size baseline The permission-repair ladder added to the review cleanup step (repair before giving up on a worktree removal, refuse the sudo leg through symlinks) plus its incident comments grew the file past its recorded size plus allowance. The growth is the fix itself — the repair logic and the rationale a future reader needs — not drift, so record the shipped size rather than trimming the rationale. * fix(review): pin the repair ladder by effect and enrich its failure warnings Review feedback on the permission-repair ladder: - Pin the ladder's effect in the contract test (three removal attempts, isolated non-sudo chmod rung, refusal-comparison direction) — the old mechanism substrings stayed green when the post-repair retry was deleted, when the non-sudo rung was deleted, and when the refusal comparison was inverted (all reproduced by mutation before the fix). - Retry the removal after the chmod rung so a chmod-repaired tree never escalates to passwordless sudo; the step comment's "each followed by a retry" is now literally true. - Strip newlines from leftover paths before echoing: leftover names are untrusted glob entries, and a fresh line on the runner's stdout would parse as a workflow command. - Both warnings now carry the deciding state: the refusal names the branch that fired; the failure warning reports the sudo probe result and the survivor's owner. - Return 0 unconditionally so a failed warning echo can never fail the if: always() job via errexit. * fix(review): close the remaining command-injection entrances in worktree warnings (#9748) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(review): execute remove_review_tree against fixtures and pin its sudo ok-state (#9748) * test(review): gate the removal-failure fixture on realpath and pin the ladder's guards (#9748) * test(review): execute the ladder's unpinned arms against behavioral fixtures (#9748) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(review): strip CR and LF from registered-worktree skip warnings (#9748) --------- Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
b5fbdb22d3
|
feat(cua-driver): add versioned Computer Use SDK and release pipeline (#9587)
* chore(cua-driver): sync upstream v0.20.0 * feat(cua-driver): add Computer Use SDK with versioned observation revisions Wrap the typed driver SDK in a standalone Node wrapper and add accessibility.observation_revision.v1: base-anchored validated diffs with opaque element tokens, explicit full-resync reasons, full-only answers on Windows/Linux. Fix portable include_screenshot schema type and classify stable kAXErrorFailure refusals as complete in the macOS capture tracker. * feat(cua-driver): complete typed Computer Use capabilities * fix(cua-driver): use Qwen-owned npm identity * feat(cua-driver): publish one Qwen CUA SDK package * fix(cua-driver): verify Windows Rust targets * fix(cua-driver): verify macOS Rust targets * fix(cua-driver): pin release Rust toolchain * fix(cua-sdk): fail closed on incomplete releases * test(cua-sdk): import workflow test globals * ci(cua-sdk): retry Debian package downloads * fix(cua-driver): harden lifecycle and release gates --------- Co-authored-by: tutu <tutu@U-RD4R9MQQ-2235.local> |
||
|
|
acc46e58cb
|
fix(autofix): give the repair pass a budget it can finish in (#9691)
* fix(autofix): give the repair pass a budget it can finish in The repair attempt ran on a hardcoded 18-minute agent budget while the primary attempt gets 120 minutes from a configurable default. Raise the repair budget to 45 minutes and carry the step and job caps that bound it. The repair attempt is handed strictly less to work with than the primary one: a deterministic rejection is an opaque check failure, not the structured review feedback the primary attempt receives, so it must first re-derive which change caused the rejection before it can amend anything. Giving that 15% of the primary budget inverted the difficulty and the allowance. Measured on four takeover PRs over nine rounds on 2026-08-21: the primary attempt reported `Autofix agent completed address-review successfully.` in 9 of 9 rounds, and the repair attempt hit `timeout (1080000ms)` in 9 of 9. Every one of those rounds discarded work the primary attempt had already finished — on #9340 a completed `origin/main` conflict resolution across three files with two mutation probes and `vitest run src/commands/review/` green at 97 files / 4335 tests. Three such rounds tripped TIMEOUT_WINDOW_CAP and parked the PR at its round cap with `autofix/needs-human`. The rejections themselves were a mix — a flaky unrelated test (#9648), a genuine defect in the PR, and a scope violation — so this is not a substitute for fixing any one of them. It is the step they all funnel through: whatever the gate rejects on, the repair attempt has to be able to finish before the round can push. Carried bounds, each preserving its documented margin: - repair step cap 20m → 55m (budget + the same 10-minute margin the primary attempt keeps, so the internal kill path still writes `agent-timeout` before the step cap fires) - review-address job cap 300m → 330m (the four long steps now sum to 305m plus the 25m setup/report reserve) - PENDING_STALE_MIN 330 → 360 (its 30-minute margin over the job cap, so a live review-address run is never aged out mid-flight) 45 minutes is deliberately a fraction of the primary budget: a repair that cannot land in 45m is a handoff, not a longer retry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VgTjRF91xANQh6SY9YGyCf * fix(autofix): carry the raised repair bounds through sibling prose * fix(autofix): revert design-record edits outside this PR's footprint (#9691) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
2a57f86198
|
fix(ci): gate the fork signal on fields the review payload delivers (#9469)
* fix(ci): gate the fork signal on fields the review payload delivers `qwen-autofix-fork-signal.yml` gated on `github.event.pull_request.maintainer_can_modify == true`. That field does not exist in a `pull_request_review` payload: the event carries the SIMPLE pull-request object, and `maintainer_can_modify` — like `mergeable`, `additions`, and `changed_files` — ships only on the full object the `pull_request` event sends. The expression evaluated to null on every delivery, `null == true` is false, and the job's `if` could never hold. Measured on the repository: across the 300 runs between the bridge shipping (#8676, 2026-08-07) and this change, 290 skipped, 7 cancelled, 1 action_required, and 0 success. Not one signal ever reached its step, so the bridge behind it has never fired either — every fork-PR review has been served by the scheduled scan alone, which is exactly the throttled backstop this bridge exists to get ahead of. The consent check is not lost, and does not move: the bridge already re-reads it live (`gh pr view --json maintainerCanModify`, then `select(… .maintainerCanModify == true)`), and that read was always the authoritative one — consent can be withdrawn between the review and the dispatch, so a payload copy could only ever have been a stale early-out. The signal job cannot make that call itself: it holds `permissions: {}`, no secrets and no checkout, deliberately, because it runs on a fork-triggered event. What the removal does cost is one signal + bridge run and one PR read for a takeover-labeled fork PR whose author has turned maintainer edits off, where the gate previously intended to spend nothing. Ordinary contributor fork PRs are unaffected — the bot-authored-or-takeover-labeled conjunct still excludes them. Also adds a regression test asserting the gate references no full-object-only field. A gate like this fails silently: the job's entire body is one echo, so "never opens" and "no fork review happened to qualify" look identical from outside, which is why this went twelve days unnoticed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * test(ci): match the full-object-only guard on a word boundary (R1-1) The guard asserted the signal gate references no full-object-only field via bare substring containment, but four deny-list entries are strict prefixes of fields the simple pull-request object DOES deliver: `merged` in `merged_at`, `commits` in `commits_url`, `comments` in `comments_url`, `review_comments` in `review_comments_url`. A future edit adding a legal conjunct such as `github.event.pull_request.merged_at == null` would turn the suite red with a message blaming a full-object-only field — pushing the author to drop the conjunct or weaken the guard itself. Anchor each check on a word boundary, and add a test that pins the matcher's discrimination on all four prefix pairs so the substring form cannot come back unnoticed. * test(ci): see full-object fields through the index operator (R2-1) The full-object-only guard matched `pull_request.<field>` literally, so it only saw the `.` property de-reference. GitHub Actions reaches the same property through the documented `[ ]` index operator, on any segment of the path, and `github.event.pull_request['maintainer_can_modify'] == true` evaluates exactly as the dot form did: the field is absent from the simple `pull_request_review` payload, `null == true` is false, and the gate never opens. The guard stayed green through it — a silent replay of the incident this PR fixes, invisible to the test written to catch it. Rewrite the index form to the dot form before matching rather than enumerating spellings, so one matcher covers every combination of the two at any depth. The word-boundary anchor from R1-1 is unchanged, so the four delivered fields the deny-list names prefix (`merged_at`, `commits_url`, `comments_url`, `review_comments_url`) still pass in every spelling. A `fromJSON(toJSON(github.event.pull_request))` round-trip still evades this; no textual guard catches that one, and the comment says so. Mutation-verified, each mutant reddening the tests that pin it: | mutant | result | |---|---| | normalization removed (identity) | 2 failed — bracket and mixed spellings go unseen | | word boundary dropped | 1 failed — `merged_at` rejected as `merged` | `npx vitest run --config ./scripts/tests/vitest.config.ts scripts/tests/qwen-autofix-fork-bridge-workflow.test.js` -> 12 passed (12). eslint and prettier clean. * test(ci): pin the fork-signal guard's bracket-whitespace tolerance R3-1: `asDotAccess` rewrites `pull_request['field']` to the dot form before matching, and its regex deliberately tolerates whitespace inside the index (`\[\s*…\s*\]`) because GitHub Actions accepts `github.event.pull_request[ 'maintainer_can_modify' ]` as a legal expression. No spelling in `referenceSpellings` carried that whitespace, so the tolerance was unpinned: deleting both `\s*` left all 12 tests green, and a later gate edit written in the spaced form would have reached the same absent field and restored the always-false gate this PR fixes. Add the spaced-bracket spelling, which both index-operator tests consume. Mutation-verified: with the two `\s*` deleted from `asDotAccess`, this file now fails 2 tests ("rejects a full-object field without rejecting the fields it prefixes" and "sees a full-object field through the index operator"); before this commit the same mutation left 12 passed. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a5d77eb817
|
chore(ci): migrate autofix prose to the design record and ratchet growth (#9677)
* chore(ci): migrate autofix prose to the design record and ratchet growth `qwen-autofix.yml` was at 462,720 bytes — 90% of GitHub's 500 KB start-runs limit and 7,280 under this repo's gate. The prose migration in #9517 had regained 78 KB; a single feature commit gave 25 KB of it back two days later, 53% of it comment lines that belonged in the design record. Nothing objected, because the gate is a ceiling and only speaks when a file is already at the wall. Two changes, addressing the level and the slope. Level: 76 comment blocks of 8+ lines move into qwen-autofix.md as af-073..af-148, each leaving its opening two lines plus the usual pointer. The file drops to 419,995 — 50 KB under the gate. Slope: every workflow's recorded size now lives in .github/workflows/.size-baseline, and check-workflow-size.sh fails a file that exceeds its entry by more than 4 KB. Growing a file is still allowed; the ratchet only insists the growth be visible in review instead of discovered at the wall. A file well under its baseline warns so the slack is reclaimed rather than banked. Two things the migration had to learn, both caught by contract tests rather than by inspection: Identical prose shares one af id. A step inlined into several jobs must stay byte-identical across copies, and minting a separate id per copy broke that for the git-config sanitize step — same length, different pointer digits. A cross-file editing contract is not prose. "This copy and the one in qwen-triage must be edited together" only does its job where the editor will see it; moving it to the design record is how a lockstep silently breaks. Blocks naming a sibling workflow or script, or spelling the contract out, stay put. Behaviour is unchanged and checked, not assumed: both YAML documents were parsed and compared with comment lines dropped from every string, and they are equal. * fix(ci): harden the workflow-size ratchet per review - key the vitest baseline lookups by separator-agnostic file name so the merge-queue Windows lane resolves entries (split('/') missed win32 joins) - fail closed on malformed .size-baseline lines: non-numeric values, leading zeros (bash octal), and extra fields no longer disable or mis-key the ratchet; keep an unterminated final line - execute the gate script end-to-end in tests (growth, missing entry, missing baseline, slack warning, malformed line) so its decision branches are witnessed - correct af-079/af-123/af-084 attributions in the design record * fix(ci): pin ratchet boundaries and skip pre-bash-4 test hosts * fix(ci): end migrated autofix teasers at sentence boundaries per review --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
ea872a4621
|
fix(review): stop the fallback comment from firing on superseded runs (#9716)
* fix(review): stop the fallback comment from firing on superseded runs The fallback-comment gate admitted every review-pr result of 'cancelled' on the assumption that a run-level cancel takes the queued fallback job down with it, leaving job-level timeout as the only live source of a cancelled review step. That assumption does not hold: the job is guarded by always(), which keeps it running through a run-level cancel. On PR #9131 a same-head pull_request_target pair started 1s apart, the newer run cancelled the older inside authorize, and the older run posted a false "review did not complete" (run 32558544379) while the surviving run was still reviewing — same head, so the in-step head-moved guard could not catch it. The two cancels are separable through needs: a job-level timeout cancels review-pr alone while its upstream jobs completed long before, whereas a run-level cancel sweeps the whole chain. 'cancelled' now opens the gate only when neither authorize nor delay-automatic-review was itself cancelled. * test(review): pin the fallback gate's upstream-cancel conjuncts Review R1-1: the resilience suite's only cancelled-related assertion — toContain("needs.review-pr.result == 'cancelled'") — matched the old and the new gate alike, so reverting the two != 'cancelled' conjuncts would bring back the #9131 false-fallback regression with the suite green. Pin the full compound clause, grouping included, so dropping either conjunct (or the parenthesization) fails the test, and rewrite the rationale comment that still asserted the falsified premise that a run-level cancel takes the queued fallback job down with it. Mutation-verified: the gate test fails on a reverted-gate mutant and passes on the PR gate. * test(review): assert the bare cancelled disjunct is absent from the gate Review R2-1: the compound-clause pin proves presence, not absence — a merge-conflict resolution keeping both sides of the gate hunk re-adds the bare "== 'cancelled' ||" disjunct beside the intact compound clause, reopening the gate on every cancelled review-pr with the suite green. Add the negative assertion; inside the compound clause the substring is followed by ' &&', so it holds on the intended gate. Also take the round-2 deferred note: the job comment now says a run-level cancel landing after the upstream chain finished still opens the gate and is suppressed by the in-step head-moved guard, and why a same-head twin cannot land that late. Mutation-verified: with the bare disjunct re-added the gate test fails on the negation; the pristine workflow passes. * test(review): pin the cancelled-check count; credit the PR-state check Review R3-2: the negative assertion rejected only one rendering of the bare disjunct — a parenthesized or respaced re-addition beside the intact compound clause escaped both pins with the gate reopened on every cancelled review-pr. Replace the negation with an occurrence-count pin: exactly one "needs.review-pr.result == 'cancelled'" in the gate catches any rendering, while the compound pin keeps guarding the conjuncts. Review R3-1: the rationale comment's list of late run-level cancels missed the closed-action flavor — a same-head cancel hours in with the upstream chain green, stopped only by the in-step PR-state check the comment never credited. Credit it. Mutation-verified: bare, parenthesized, and reverted-conjunct gate mutants each fail the gate test; the pristine workflow passes at base parity. |
||
|
|
10c647f320
|
refactor(core): remove root barrel self-imports and enforce the boundary (#9635)
* refactor(core): remove root barrel self-imports * fix(core): activate root barrel boundary rule * test(core): cover root barrel export visitors * fix(core): remove unused boundary constant * fix(core): correct misplaced imports flagged by root barrel rule coreToolScheduler: ToolExecutionStatus is exported from core/turn.ts, not tools/tools.ts; was also duplicated in the tools import block. nonInteractiveToolExecutor: RuntimeContentGeneratorView is exported from agents/runtime/agent-context.ts, not core/contentGenerator.ts. Both were surfaced by the new no-core-root-barrel-import rule in #9152. * fix(core): complete telemetry mock and harden barrel rule edge cases extensionManager.test: the ../telemetry/loggers.js mock was missing logExtensionInstallEvent, logExtensionUninstall, and logExtensionDisable, which the source now imports from that path after the barrel cleanup. This caused 53 test failures. no-core-root-barrel-import.js: added package-specifier detection (@qwen-code/qwen-code-core), template-literal dynamic import support, depth-0 ./index.js handling, and __tests__ exemption parity with the test-file check. Added trailing newline. Tests: added coverage for all new edge cases in both rule test files. Refs #9152 * fix(core): close package-level barrel gap and coverage holes The barrel rule blocked only specifiers resolving to the src-level index.{js,ts}; the package-level barrel (packages/core/index.ts, which re-exports the src barrel) remained reachable via one extra ../ hop. Match ../index.{js,ts} so core production files cannot re-import the root barrel through that path. Also: hoist the duplicated src-root marker to a single constant, reword the rule message so it no longer hardcodes ../index.js, pin the .ts spelling branch, fix the vacuous fixtures-exemption case, and add a cross-directory flat-config fixture so a narrowed files glob fails the integration test. Refs #9152 * fix(core): flag barrel subpaths and inline type imports in the root barrel rule * fix(core): harden no-core-root-barrel-import rule Anchor the source-root marker on its last occurrence so nested checkouts resolve correctly, reject relative imports of the compiled dist barrel, and treat .spec files as tests. Adds regression coverage for each. |
||
|
|
b455bad5e5
|
refactor(cli): keep acp-integration off serve internals (#8084) (#9144)
* refactor(cli): keep acp-integration off serve internals (#8084) The dependency direction set in #8084 regressed: native Live Voice ( |
||
|
|
5efb42458a
|
fix(autofix): pass CI=true through the gate's env -i launches (#9649)
* fix(autofix): pass CI=true through the gate's env -i launches
The verification gate launches (first pass + repair pass) run the branch's
build/typecheck/lint/test through an env -i clean child that allowlisted
only 8 variables and dropped the runner-provided CI=true. Without it the
gate's checks run with inverted CI semantics relative to the repo's
regular CI: packages/cli/src/ui/auth/AuthDialog.test.tsx skips 18
TUI-input tests on CI as unreliable, and without CI=true they un-skip
inside the gate and one flakes (~5s vi.waitFor) — reject_fix fires
retryable on a fix the PR's own CI passes green, burning the repair pass
and mislabeling the round's A/B baseline.
Add CI="${CI:-true}" to both env -i allowlists (probe: CI=true → file
green; env -u CI → the TUI test fails 1/25), and pin the full allowlist
contents in the contract tests — the old pin counted env -i occurrences
only, so a missing variable shipped green.
Follow-up from PR #9262 (R5-1); issue #9648.
* fix(autofix): widen gate allowlist pins to lowercase env names
The allowlist pins extract passed variables with [A-Z_][A-Z0-9_]*, so a
lowercase or mixed-case entry — e.g. npm's own npm_config_* convention
— is invisible to the sorted-multiset check: adding one to a single
launch ships green, and only a later asymmetric drop then fails,
producing exactly the divergent-environment regression the pins exist
to catch while CI stayed green the whole way.
Widen the name class to [A-Za-z_][A-Za-z0-9_]* in both pins — the gate
launches pin added in 8db672e80c and the sibling run_deferred_upsert
pin that shares the identical regex and blind spot (probe: inject
npm_config_registry="..." into one launch → old regex 215/215 green,
widened regex fails with + "npm_config_registry" at each pin; pristine
workflow stays green).
Review round 1 finding R1-1.
* fix(autofix): pin the gate clean-child launches structurally (#9649)
* fix(autofix): pin the gate run-body statement list around the launch (#9649)
* fix(autofix): pin gate startup channels and slash-path the digest check (#9649)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(autofix): pin CI at step level in both verification gates (#9649)
* fix(autofix): drop shadowable gate-body pins, pin gate HOME from staging (#9649)
The two gate bodies' leading statements were bare command words: bash
imports $GITHUB_ENV-planted BASH_FUNC_export%%/BASH_FUNC_unset%% (or
BASH_FUNC_builtin%% for a builtin-prefixed spelling) as functions at
startup even under --norc, and a shadowed pin can arm a DEBUG trap that
swaps the staged runner after the digest check passes and before the
env -i launch executes it — forging the verdict that gates the PAT push.
Both statements are redundant: PATH reaches the child through the env -i
allowlist, and LD_* is closed by the step-level pins, the env execve
prefix, and env -i. Probed: hostile plants fire on the pre-fix body and
are inert on the fixed body; child env is byte-identical without them.
HOME was the remaining $GITHUB_ENV channel into the gate child: npm
resolves its userconfig from HOME, and a planted HOME's .npmrc
script-shell wraps every verdict-determining npm run, so a red branch
reports green (probed: exit 7 becomes exit 0). Capture HOME at stage
time, before any branch code runs, and pin it at step level in both
gates — the trusted_path doctrine. Contract test updated in lockstep:
the pinned statement list drops the two entries, and the pin assertions
cover the HOME pin and its stage-time capture (mutation-probed).
---------
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
||
|
|
cef54b8340
|
refactor(autofix): move the push-and-report body out of the workflow file (#9653)
* refactor(autofix): move the push-and-report body out of the workflow file qwen-autofix.yml was 462,656 bytes — 90% of GitHub's 500 KB start-runs limit and inside the 470,000-byte gate's warning band. Past that limit GitHub stops starting runs and says nothing: schedule ticks vanish, dispatches sit queued with zero jobs, and only PR-event runs keep working because they resolve an older copy from the PR's branch. The file crossed it once already, on 2026-08-19, and the loop went dark for a day. 'Push and report' was the largest single block in the file at 626 lines of inline shell. Its body moves to .github/scripts/autofix-push-and-report.sh byte-identically — the YAML keeps the step's if: and env:, which is where the questions of when it runs and what reaches it belong. The file drops to 422,447 bytes. Extraction moves the trust problem with it. By the time this step runs the agent and the verification gate have executed branch code on this host, so the workspace copy is branch-controlled. The step runs the trusted-base copy staged before any of that, after proving its digest — recorded in GITHUB_OUTPUT, which a disk write cannot reach — still matches, that the staged path is a regular file, and with both reads bounded so a planted FIFO is a refusal rather than a hang until timeout-minutes. A new contract test pins all of that, including that the workspace copy is never what runs; each of its assertions was checked against a mutant of the line it guards. The census assertions that counted sites across the workflow now count the script too, so an extraction cannot silently drop a site from a count that exists to pin it. The two emit_growth_audit_marker copies are compared dedented, since one is now at column 0 and its twin is still at ten. This is also the step docs/design/autofix-gate-runner-isolation.md moves into its own publish job. Carrying it as a file makes that a small diff, and deletes the staging scaffolding added here: a job that checks out the trusted base and never executes branch code can run the script where it lies. Refs #9089. * fix(autofix): close the push-and-report gate's env-import and TOCTOU holes The wrapper verified the staged script in the step's inherited shell: a $GITHUB_ENV-planted PATH or BASH_FUNC_* import could swap the gate's own words, and the staged path was opened three times, so a watcher could swap the file between the digest check and the execution read. Run the whole verify-and-run sequence in one env -i clean child that opens the staged path once, verifies the digest of the captured bytes, and executes those same bytes. Also fail staging closed on any cp failure, fix the script's env contract, and pin the new guards. * fix(autofix): close the push-and-report gate's silent no-op hole Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(autofix): pin gate pass-through, refusal order, and continuation bans (#9653) Address review suggestions on the push-and-report extraction: - R2-3: pin the gate child's env -i pass-through list as a sorted allow-list equality check (R9-10 precedent), so a dropped variable fails loudly instead of expanding empty. - R4-4: pin the sentinel refusal BEFORE the round_reported write — shape-only pins let the ordering rot (probe-verified mutant). - R4-6: make the execution bans continuation-tolerant; bash joins `\<newline>` before tokenizing, so line-continued spellings evaded the `[^\n|]*` middle (probe: old regex misses, new catches). - R1-6: re-scope the paired R10-8 upsert negatives to both halves, matching the positive censuses. - R3-2: correct the stale workflow-size figure (462,656 -> 463,004 bytes at the post-merge base; 90%/98% still hold). - R1-5/R1-15/R1-11: correct three disproved claims in the trust-model doc (block rank, GITHUB_OUTPUT qualifier, which call sites hold the single-open shape). * refactor(autofix): deliver the push-and-report body as content, not a staged file The extraction put the body in a file and then had to defend that file: a staged copy under RUNNER_TEMP, a digest recorded in step output, a type check, bounded reads, and — after three rounds of findings against those — an env -i clean child with a liveness sentinel and a 38-entry pass-through list wrapping the whole verify-and-run sequence. Four review rounds have now been spent on that machinery, and the open findings against it are about the machinery, not about the move: output buffering that costs the live log the inline body had, an env allow-list nothing pins, and pin gaps in the tests that exist only to hold the gate together. Delete the object being guarded instead. The stage step already reads the trusted base before any branch code runs, so it captures the body as text into step output and the step runs those bytes — the delivery the inline block had, and the one upsert-deferred-issue.sh already uses. With no agent-writable copy on disk there is nothing to stage, digest, type-check or re-open, and no check→use window between the steps: R1-2's TOCTOU, R1-13's FIFO double-open, R2-2/R2-5/R2-6/R2-7's single-open pins and R4-1's buffering all describe a mechanism that no longer exists. This is not a security regression. What it drops is protection the inline block never had; what it keeps is the one property that matters — the bytes come from the trusted-base checkout through expression context, not from a path the branch can write. The step shell's inherited environment (R1-1, R1-3) is the status quo this PR preserves rather than the thing an extraction should be fixing: #9525's publish job takes the PAT out of that shell entirely, which is the real close. Kept from the gate work: the round_reported output. A loader plant that kills this shell at execve exits 0 having written nothing, so 'Finalize autofix status comment' can still tell a published round from a no-op — now without a sentinel, because the write is simply last. Also corrected, from the same review: the header no longer claims the block was the largest in the file (it is third, after 'Scan for PRs with new feedback' and 'Prepare branch and feedback'), the size is measured at this branch's merge-base (462,720), the env contract is stated as a rule rather than a list that went stale within one round, and the shellcheck note says which lane and which severity it is clean under. The R10-8 execution-ban negatives now cover the scripts as well as the workflow, and the script's own flag line is pinned as a whole rather than by spelling. Refs #9089, #9525. --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
f89335e453
|
fix(autofix): stop counting idle timeouts toward the timeout cap (#9673)
* fix(autofix): stop counting idle timeouts toward the timeout cap TIMEOUT_WINDOW_CAP exists to stop a PR that is too big to finish a round inside the agent's time budget, and its remedy says exactly that: split the PR or raise the budget. A silent-sandbox (idle) timeout is a different failure entirely — the idle watchdog kills the round because the sandbox produced no output at all, so no budget increase cures it and nothing about the PR caused it. Counting a failure whose prescribed remedy is inapplicable is what parked healthy PRs: over the 14 days to 2026-08-21 idle rounds were 58 of 119 timeouts, every one of the 51 windows that tripped this cap tripped it at exactly three, and 9 of the 12 PRs then carrying autofix/needs-human had been stopped here — #8332 at 24 rounds, #8368 at 28, #8276 at 16, all still pushing rounds when they were parked. Gate the cap on budget timeouts alone. A persistently wedged sandbox stays bounded, because an idle round pushes nothing and resets no streak, so CONSECUTIVE_FAILURE_CAP still terminates it; what no longer terminates is idle rounds interleaved with real progress, where the PR is not stuck and the runner is. Idle rounds stay visible through a job-log warning, which reaches whoever owns the runners without spending a comment on someone's PR. Two consequences inside the census. The idle needle became the full emitted headline prefix rather than a bare substring, because the count is now subtracted and must be a subset of the total — a loose needle could match provider error text on the same line and drive the difference negative. And the all-idle remedy branch is gone as unreachable: the guard now fires only when budget timeouts alone reach the cap, so a counted window always holds more of them than idle ones. * fix(autofix): emit idle census warning on terminal runs (#9673) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(autofix): pin idle-census counts and extend idle exclusion to prepare census (#9673) * test(autofix): reuse pinned IDLE_HEAD fixture in timeout-census replay (#9673) --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
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> |
||
|
|
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. |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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.
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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 |
||
|
|
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 |
||
|
|
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. |
||
|
|
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. |
||
|
|
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 (
|
||
|
|
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
|
||
|
|
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> |
||
|
|
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. |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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 |
||
|
|
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> |
||
|
|
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> |
||
|
|
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. |
||
|
|
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. |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
c031cc6279
|
fix(ci): self-heal failed checkouts on the reused review runners (#9220)
* fix(ci): self-heal failed checkouts on the reused review runners A checkout failure on the self-hosted review pool was terminal: either a transient network drop mid-fetch (curl 92 / early EOF), or a corrupt persisted workspace whose refs claim objects missing from its object store, after which every fetch dies in negotiation with 'remote did not send all necessary objects'. ecs-qwen-runner-64c-23 stayed in that state for two days (2026-08-13..15), failing seven review jobs on the same missing SHAs. Make the first checkout continue-on-error; on failure wipe the whole workspace (not just .git) and retry the identical checkout once. The workspace is disposable — later steps reinstall deps and tools. * fix(ci): heal with the pool wipe idiom and pin the checkout guardrails (#9220) * fix(ci): pin the heal chain's sudo leg, path guard, and survivor signal Addresses the 16:40 review round on the checkout self-heal: - The wipe-failure test leaned on the real sudo, so it covered a different branch per lane; replace it with a PATH-stubbed sudo that forces both legs to fail hermetically, and pin the survivors left in place plus their oncall-visible warning. - The '|| sudo -n find' escalation leg survived deletion mutants: add a stub-sudo test proving the leg actually runs when user-mode find fails (leg-deletion and '||'->'&&' mutants both verified red). - Reuse the triage idiom's suspicious-path guard before wiping. - Count post-wipe survivors and warn with the count — triage exits 1 here, but the heal chain must stay alive for the retry. - Disclose in the step comment that the sudo leg only helps pool members with passwordless sudo. * fix(ci): close the heal guard's trailing-slash hole and name wipe survivors (#9220) * fix(ci): canonicalize the heal guard's path match and allowlist the runner workspace (#9220) * fix(ci): strip the heal allowlist root's trailing slashes and pin the guard layers (#9220) * fix(ci): canonicalize the heal lock fixture and pin the WS strip loop (#9220) * fix(ci): keep the checkout-heal suite green on a BSD userland Addresses the 09:38 review round: three of the new tests assume the wipe script's `realpath -m` canonicalization actually ran, and `-m` is a GNU coreutils extension — Darwin ships FreeBSD's `realpath [-q]`, exits 1 on it, and the script's `|| printf` fallback silently keeps the raw path. The production script is unaffected (the review pool is Linux-only), but this suite is excluded on win32 alone, so it also runs on the macOS lane, where the assertions are red for a defect that cannot exist there. - Probe the host for `realpath -m` and skip the canonicalization test when it is absent, rather than skipping on `platform === 'darwin'`: the probe keeps the coverage on a Mac with coreutils on PATH and still skips on any other non-GNU userland. Mutation-checked on a GNU host — deleting the canonicalization line still turns the test red. - Spell both halves of the allowlist comparison the same way in the lock fixture: it resolved its workspace with realpathSync while the runner-workspace root stayed raw, so on a symlinked tmpdir (macOS /var -> /private/var) the two sat on opposite sides of the link, the guard refused, and the wipe helper threw before any assertion ran. Canonicalizing the root keeps both tests running everywhere instead of leaning on the GNU-only flag to reconcile them. - Record in the step comment that `-m` is GNU-only and that off-GNU the guard degrades to the strip loop and the allowlist. Verified by simulating a BSD userland (a PATH-fronted realpath that rejects -m): the suite goes from 1 failed / 12 passed to 12 passed on the CI lane's environment, and from 3 failed / 10 passed to 12 passed with a symlinked TMPDIR, matching the two failure shapes reported. --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |