mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-09-11 19:46:21 +00:00
671 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6652fdc9f6
|
refactor(core): centralize worktree config derivation (#9915)
* refactor(core): centralize worktree config derivation * refactor(core): centralize agent config derivation (#9918) * refactor(core): centralize agent config derivation * refactor(core): centralize scoped config profiles (#9920) * fix(core): keep approval-mode transitions working on derived teammate and dispatch configs Give every per-agent config child-local approval state, not just the ones spawned with an explicit mode: tools bind to the config and teammate mode switches (Shift+Tab) plus 'Proceed always' confirmations call setApprovalMode on it, which the derived-Config guard rejects without an approval profile. Snapshot the base mode when none is requested so no AUTO strip is acquired and cleanup stays a no-op. Layer an approval profile over the workflow-orchestrator's derived dispatch contexts (worktree isolation / workingDir) as agent.ts does, with cleanup in the outer finally. * fix(core): anchor plans-directory assertions at the plans-owning base Config Derived agent profiles rebind targetDir to their own workspace, but the plan file stays in the owning base's configured plans directory. The containment assertions in savePlan/loadPlan compared against the derived workspace and threw FatalConfigError for teammates whose cwd differs from the parent project root, which savePlanBestEffort swallowed into a debug warning — silently dropping the plan. Resolve the anchor to the Config that owns the plans-directory state instead. * test(core): cover deriveAgentConfig workspace rebinds Mirror the deriveWorktreeConfig pairing test: assert the public getter overrides (getCwd/getProjectRoot/getWorkingDir/getTargetDir, plan-file path) and the paired private targetDir/cwd field writes plus own workspaceContext/fileDiscoveryService, so a refactor that drops any rebind fails loudly instead of leaking parent-tree reads into spawned agents. * test(scripts): add flat-config integration test for no-config-object-create Mirror no-core-root-barrel-config.test.js: load the real eslint.config.js via new ESLint({overrideConfigFile}) and lintText virtual packages/core/src/** paths, so a future edit to the flat-config wiring (plugin key, rule name, ignores) that silently stops the gate applying to packages/core/src/** turns this test red instead of degrading the invariant with no signal. --------- Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
f7d39630fe
|
fix(autofix): raise the repair and develop-issue agent budgets (#10217)
* fix(autofix): raise the repair and develop-issue agent budgets The repair pass after a deterministic rejection timed out on 48 of 87 attempts this week even at 45 minutes, while the 28 that landed took p50 31m / p90 41m — the successes were brushing the cap. Raise it to 60m and move its step backstop to 70m to keep the 10-minute margin; the review-address job cap follows to 345m (sum of bounded steps + 25m reserve) and the scan's pending-check staleness bound to 375m. The runner default, which only the issue lane's develop leg relies on, goes from 50m to 90m: 6 of 13 develop attempts died at the budget with the branch discarded, against 2 that published a PR. Candidate assessment is pinned to its former 50m explicitly so the issue job's worst case stays under its 180m cap and the always() claim-withdrawal reporter is never cancelled by a job timeout. * fix(autofix): witness assess budget pin, correct calibration prose (#10217) The assess-candidates step's 50-minute QWEN_TIMEOUT_MS pin — the only guard keeping it off the runner's new 90-minute default — had no test witness: deleting the line left the whole suite green. Pin it, and pin the af-101 design-record horizon to the gate's own PENDING_STALE_MIN bound (375m) instead of the review-address job cap, so the doc and the bound cannot drift again. Also correct the idle-watchdog calibration comment: the fleet's shortest leg is now the issue lane's 50-minute assess pass, not the 60-minute repair pass. * test(autofix): witness develop-issue leg inherits runner default budget (#10217) --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
a8eff435d2
|
feat(review): keep deferred suggestions recoverable off the PR page (#9761)
* feat(review): keep deferred suggestions recoverable off the PR page Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(review): keep contributor-controlled content out of the review-record channel Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(review): keep stale and forged content out of the review record * test(review): pin the read-limit marker strip, findings artifact name, and retention window Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(review): harden the review-record channel against stale, planted, and forged content Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(review): close report-prose forgery and runner command-file poisoning Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(review): close the runner command-file and PATH channels by construction Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(review): supply PROXY_BIN to the retry-loop harness The round-8 decoy wiring in the review step's agent invocation expands $PROXY_BIN for the GITHUB_PATH/GITHUB_ENV decoys, but the contract harness extracts the retry loop starting at OUTCOME='' — past the step's PROXY_BIN initialization — and set -u aborted every scenario with an unbound variable (25 red tests on CI, all classified as 'Qwen review exited with status 127'). Supply the variable the real step always sets. * fix(review): stage artifacts in a per-run dir; coerce script-lint fields Two review findings, both verified in code before fixing: R9-2 — the fixed staging path could arrive pre-occupied by foreign-uid residue a previous containerised job left in RUNNER_TEMP and this job cannot remove; the unconditional upload would then publish that residue as this run's record. Stage into a fresh mktemp dir (unpredictable, 0700) and hand the path to the upload step via GITHUB_OUTPUT; an unset output interpolates empty and the upload errors into continue-on-error, uploading nothing. The old fixed path is swept after the upload. The round-2 wipe-before-guard property is closed by construction instead: residue is never read, so it can never publish. Contract suite reworked to the mktemp semantics. R9-1 — the scriptLintGate prose legs passed report fields (read with JSON.parse and no runtime validation, from a side file the review agent can rewrite) straight to stripCommentGrammar, so a non-string or missing reason/tool threw a TypeError out of composeReviewBody — losing the whole round, Criticals included, where every other malformed shape in the module degrades to a cap. Coerce with String() like mdField does; regression test covers all four malformed shapes. * fix(review): fail closed on a structurally malformed script-lint report Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(review): unbreak the cli build (TS4111) and neutralize comment grammar inside the sanitation fixpoint Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(review): neutralize comment grammar at the verbatim body exits, not inside the shared fixpoint Under `review.attribution` off, the verbatim body exits ran `stripForUnattributedPost` first and `stripCommentGrammar` after. The attribution strips match on the displayed projection, which drops an HTML comment whole, so a forged footer wrapped as `<!-- _— … via Qwen Code /review … -->` passed the fixpoint untouched — and the grammar strip then materialized it as visible text in the one mode that exists to post none (pre-neutralization the wrapper rendered as nothing). The same order reached the cannot-tell and duplicates lists through `asListLine`, and the ledger title through `buildLedger`. The previous commit closed this by weaving `stripCommentGrammar` into the fixpoint chain itself. That chain is shared, and the weave broke two of its other callers: submit's inline-comment transform, whose pinned contract keeps a quoted marker MENTION verbatim (`posts <!-- qwen-review suggestion --> verbatim` is text, not a bare marker — the existing submit.test.ts pin went red), and the ledger's id read, which steps over a leading comment as render-nothing residue — `<!-- x --> R1-2: …` in a body Critical became the words `x R1-2: …` ahead of the id and the finding renumbered to R2-1 while the posted item still said R1-2. So the weave comes back out, and one helper, `quotedProse`, states the order at the three exits and the ledger title instead: comment grammar inert first, then the mode's strip (the full fixpoint chain when attribution is off), then the trailing footer strip both modes share — the shape submit's post transform already uses. The marker-LINE strip is the one strip that acts on comment grammar itself, so it runs ahead of the neutralization: a transcribed posted comment's trailing `<!-- qwen-review … -->` line keeps dropping instead of posting as visible words. The ledger reads the carried id before the grammar goes inert and neutralizes the title after, so the residue stays invisible to the anchor and the title still matches the rendered item. Pins: the wrapped footer strips from every verbatim exit (attribution off) and like an unwrapped one under attribution on (only the canonical footer posts); the ledger title matches the visible item in both modes; a transcribed marker line in a duplicates entry still drops; a carried id behind comment residue in a body Critical survives; and the fixpoint itself leaves comment grammar alone. * fix(review): close comment grammar the strip chain splices back together `quotedProse` neutralized comment grammar once, ahead of the attribution strips, and posted whatever the chain returned. The chain's removals splice: cutting two footer spans out of `x <!-‹span›- qwen-review-deferred --‹span›> y` joins `<!-` to `- … --` to `>` and re-forms a live `<!-- qwen-review-deferred -->` (or a forged `<!-- qwen-review-ledger` opener) that the grammar strip never saw — no delimiter existed when it ran. The bodyCriticals exit posted the result raw: two live deferral markers on a round that deferred a Suggestion, a lone one on a round that did not, and a forged ledger opener ahead of the canonical marker for the next round's strip to take first. A trailing grammar strip alone would trade one forgery for another: neutralization JOINS as well — `via Qwen<!-‹span›-Code /review` strips to `via Qwen<!--Code /review`, which a final grammar strip turns into the footer phrase the chain had already finished looking for, posting a forged footer in the mode that exists to carry none. So the exit repeats neutralize-then-strip until nothing changes. Every strip in the chain deletes or leaves its input alone and none lengthens, so the loop ends within the entry's length; at the fixpoint every step is the identity on its input, so the result carries no comment grammar, no marker line, no footer span and no trailing footer at once. The shared fixpoint in review-footer.ts is untouched — submit's inline transform and the ledger's id read keep their contracts. The ingest gate projects through the same closure, beside the as-written projection it already had: an entry held up only by a footer that comment grammar had split renders visible words as written and strips to nothing at the exit, and would have posted as an empty body Critical that still counts toward REQUEST_CHANGES. The workflow-size baseline records the shipped size of qwen-code-pr-review.yml (165876), as the ratchet's rule asks. Pins: the spliced deferral marker goes inert at all three verbatim exits with exactly as many live markers as the round warrants (0 listless, 1 with a deferral); a spliced ledger opener neither forges nor swallows the real ledger; the joined footer strips from every exit; and the gate refuses an entry that is nothing but a split footer, in both modes. --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> |
||
|
|
d526612e92
|
chore(desktop): drop stale packages/desktop references after Electron removal (#10021)
* chore(desktop): drop stale packages/desktop references after Electron removal Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * chore(desktop): drop remaining packages/desktop mentions in find-simplifications skill Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * ci: point the windows test job's temp at a short-alias-free path * ci: restrict the windows temp redirect to the hosted lane On the self-hosted lane configure-windows-runner appends TEMP/TMP=$env:RUNNER_TEMP afterwards and the runner's last env-file write wins, so the short-alias redirect step only created an unused directory and dead env writes there. Gate the step to the hosted fallback lane, add -Encoding utf8 to the Out-File writes to match the configure action's test-pinned convention (shell 'powershell' is PowerShell 5.1, whose Out-File default is UTF-16LE), correct the comment's "sibling of the workspace" to the child directory the code creates, and pin the gate and the spelling in the no-AK CI wiring test. * test(ci): pin the windows temp redirect's remaining load-bearing lines --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
2bd0ff923e
|
fix(ci): exempt off-Linux E2E shards from vitest RPC-timeout unhandled errors (#10097) (#10112)
* fix(ci): exempt off-Linux E2E shards from vitest RPC-timeout unhandled errors (#10097)
The post-merge E2E run on
|
||
|
|
35d7ad1c56
|
ci: point the windows test job's temp at a short-alias-free path (#10034)
* ci: point the windows test job's temp at a short-alias-free path * fix(ci): preserve alias-free Windows temp paths * fix(ci): preserve self-hosted Windows temp path * fix(ci): compare Windows temp against its realpath case-insensitively The short-alias guard compared TEMP/TMP to fs.realpathSync with a strict !==. On Windows realpathSync returns the on-disk casing, so a drive-letter or directory-case difference — the same directory under one spelling — failed the job for a reason unrelated to the 8.3 alias. The guard runs on both lanes, so that false positive could red-wall the self-hosted lane against its own pre-existing RUNNER_TEMP. Compare case-insensitively instead: RUNNER~1 -> runneradmin differs by more than casing and still fails, while a casing-only difference warns and passes. An unset TEMP/TMP now reports itself instead of surfacing as realpathSync(undefined)'s TypeError. The test now extracts the script from the workflow and runs it against symlinked temp dirs, covering all three outcomes, rather than pinning the JS text — a substring pin cannot tell a working comparison from a reverted one. * test(ci): limit temp alias simulation to Linux --------- Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: yiliang114 <jinjing.zzj@gmail.com> |
||
|
|
4736927000
|
fix(ci): verify previous release tag exists in git before anchoring release notes (#10167)
* fix(ci): verify previous release tag exists in git before anchoring release notes * test(ci): cover the no-stable-git-tags branch of previous tag resolution * fix(ci): filter unparseable tags and pin semver sort order - Add semver.valid() guard to listExistingStableTags so tags with leading zeros (e.g. v01.2.3) are dropped instead of crashing the sort - Add test for unparseable-tag filtering (v01.2.3 in tag list) - Add test for semver vs lexicographic sort (v0.9.0 vs v0.10.0) |
||
|
|
fc0e827658
|
fix(ci): run the autofix scan lane on the persistent pool (#10055)
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 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): run the autofix scan lane on the persistent pool route and review-scan ran on GitHub-hosted runners. On 2026-08-25 a hosted-runner backlog queued route for over 20 minutes — longer than the cron period — so each newer scheduled tick superseded a still-queued one under the af-005 rule, and nine consecutive schedule runs died without scanning while the ecs-qwen pool stood mostly idle. No review-feedback work was dispatched for hours. Move both jobs onto the persistent pool with the exact fork-trust and kill-switch expression the heavy jobs already use; the command jobs stay hosted. Adds the af-148 rationale and routing regression pins. * fix(ci): harden the scan lane for the persistent pool PR review: moving route and review-scan onto the pool exposed their gh calls to the shared, attacker-writable $HOME and left the scan's per-run WORKDIR uncleaned. Mirror the heavy jobs' gh reroute preamble in both steps, give the scan a reaper-visible WORKDIR created with umask 077 plus an always() cleanup step, extend the kill-switch test to all four events, correct the substitution-order comment, and pin the scan lane's no-checkout carve-out premise. * fix(ci): close the scan workdir's remaining pool-hygiene gaps Address round 2: pre-clean WORKDIR before creating it (run_id is public; mkdir -p alone accepts a pre-planted dir or symlink on the shared /tmp), move the fleet file inside WORKDIR so the always() step and the autofix* age sweep reclaim it when the EXIT trap cannot, fold the scan lane's gh hardening pins into the PAT-step loop so the preamble has one edit site, and pin what a mutant could previously drop silently: the WORKDIR value's autofix* prefix, the pre-clean ordering, the fleet-file home, and the cleanup step's actual rm -rf command inside its own slice. * fix(ci): pin the age-sweep reclaim consumer and re-bump the size baseline Round 3 review: the reclaim-contract pin covered only the producer side (the scan WORKDIR basename). Pin the consumer — the heavy jobs' age-sweep line — inside both Reset autofix workspace step slices, so narrowing the glob or moving the sweep ships red. Also pin the scan lane's EXIT trap line (the in-step cleanup twin of the always() step), and re-bump .size-baseline to the workflow's actual 404284 bytes, which f9e56f46's 15 lines left under-recorded by 679. * fix(ci): pin issue_comment in the scan-lane routing tests Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(ci): fail loud when the scan lane's gh config dir cannot be minted export VAR="$(mktemp -d ...)" reports export's status, not the substitution's: under bash -e a failing mktemp continued with an empty GH_CONFIG_DIR, and gh treats that as unset — falling back to the shared, attacker-writable ~/.config/gh the route and review-scan preambles exist to close. Check the assignment, ::error + exit 1, then export, and pin the abort behaviourally: both steps run under a mktemp that refuses only the gh-config template must fail before any gh call — removing the guard turns the witness red (probed). Also pin the issues trigger in the scan-lane routing matrix: without it an allowlist narrowing of the pool clause demotes issues back to hosted while the suite stays green (probed by mutant). --------- Co-authored-by: wenshao <nigolaschao777@gmail.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
aa7a0f0543
|
fix(review): give cancelled runs an accurate fallback body instead of the failure comment (#10114)
The fallback-comment gate admits a cancelled review-pr on purpose: a job-level timeout is auto-CANCELLED by GitHub (failure() false), which opens neither the failure-only gate nor the in-job step, so silence there would leave a timed-out review unexplained (#9255). But the same 'cancelled' result also arrives when a run or job is cancelled mid-review after the upstream chain finished, and that flavor got the full failure body — "The review pipeline failed before a review could be posted. A transient error is retried automatically…" — none of which is true for a cancellation. On PR #9729, run 32875478404 was run-cancelled two minutes into the review with no successor run, so the #9716 supersede guard correctly did not match, and the comment read as a pipeline outage to the PR author. The two flavors are not separable in needs — both reach the gate as review-pr 'cancelled' with upstream green — so the fix branches inside the step on the wired-in needs result: a cancelled review-pr now posts one body accurate for both flavors (no failure/auto-retry claims, retry instruction kept for the timeout flavor, run-URL markdown link kept for the cross-job dedup), and everything else keeps the failure body. The step runs under set -u, so a dropped env wiring fails the step loudly instead of silently reverting cancelled runs to the false body. Mutation-verified: neutralizing the cancelled branch fails both new tests; the restored workflow passes the suite at base parity. Fixes #10109 |
||
|
|
b22471c4e7
|
chore(ci): migrate CUA npm packages to trusted publishing (#9836)
* chore(ci): remove stale NPM_TOKEN references from cua-sdk publish * test(scripts): pin cua sdk trusted publishing workflow * test(scripts): pin cua sdk publish auth contract * chore(ci): migrate node repl publish to trusted publishing |
||
|
|
fc874dfe0b
|
fix(ci): retry sandbox image builds and file an issue when a release build fails (#9916)
* fix(ci): retry sandbox image builds and file an issue when a release build fails The v0.22.0 tag build died on a transient ETXTBSY during `npm ci` and was never retried, so ghcr.io/qwenlm/qwen-code:0.22.0 was never published while npm already served 0.22.0. Every sandbox-based CI lane (/resolve, sandboxed review, autofix) then crashes with "manifest unknown" until the image exists. Add one bounded retry to the buildx step: the first attempt carries continue-on-error so a successful retry turns the job green, and the retry gates on the first attempt's outcome alone (a failure() gate would read false once continue-on-error absorbs the first attempt). The publish condition is shared through one job-level env so the two build steps cannot drift. Add a follow-up job that files or updates one issue per version when both attempts fail — for tag pushes and for publishing dispatches alike, since the issue body itself recommends that dispatch as the recovery path. Dedup uses an exact body marker matched client-side, because GitHub search tokenizes the colon out of the marker and never finds these issues. Extend the existing workflow gate test to pin the retry contract and the issue-job gate. Fixes #9898 * fix(ci): move the image-build failure issue logic to .github/scripts/ The workflow-size ratchet rejects growth past the recorded baseline +4096 bytes; the inline issue-filing step grew build-and-publish-image.yml by ~5.2 KB. Move the step body to .github/scripts/image-build-failure-issue.sh (the gate's own recommended remedy), leaving the job as a thin env + script call. No behavior change; the gate test now pins the script call and reads the dedup contract from the script. * fix(ci): grant the failure-issue job contents permission and normalize dispatch versions * test(ci): pin the failure-issue gate and retry step invariants * fix(ci): gate the failure-issue job on the exported publish decision * fix(ci): skip the failure-issue job for versionless publishing dispatches * test(ci): pin the PUSH_IMAGE value and the login gate at the definition site * test(ci): replay the image-build failure-issue script under a gh stub * fix(ci): describe release build job failures without asserting a buildx cause * fix(ci): preserve annotations and recorded runs when updating the failure issue * test(ci): pin the dedup label on create and the open-state filter on lookup * fix(ci): document the pre-first-step gap in the failure-issue gate A build job that fails before its first step runs (runner provisioning failure) never executes publish-decision, so push_image stays empty and file-failure-issue is skipped despite failure() being true. Closing the gap structurally would restate the publish predicate and re-introduce the drift this PR removes, so document it on the job comment instead: future "failed publish, no issue filed" investigations start here, and a scheduled npm-vs-GHCR reconciliation remains the backstop. * fix(ci): record build-and-publish-image.yml's shipped size in the workflow size baseline The retry logic and failure-issue filing steps added by this PR grew the workflow from 4638 to 8887 bytes, past the 4096-byte allowance. Record the new size so the size gate passes. * fix(ci): harden the image-build failure reporter per review round 4 (#9916) - Replace GNU-only `head -n -1` with POSIX `sed '$d'` so the stranded-heading strip no longer corrupts the body on BSD userland (R4-1). - Skip the bash replay suite on win32, where backslash RUNNER_TEMP and the ';'-separated PATH cannot express it; the YAML pins still run there (R4-2). - Re-check head readability AFTER the normalization strip, which can itself empty the head and used to drop the narrative permanently (R4-8). - Admit only recorded-run shapes into the machine block so a bullet-shaped human annotation is no longer reordered into it or clipped by the cap (R4-13). - Remove the marker-restore branch: with the run shape pinned, every body that matched the dedup carries its marker in head+tail, so it was unreachable (R4-9). - Cross-reference the sibling split/merge contract in both implementations (R4-5), disable SC2016 with rationale on the literal-backtick formats (R4-6), and document the label-removal residual gap on the job (R4-11). - Behavioral witnesses: run-cap, stranded-heading, marker-survival, empty-head and empty-after-strip prose fallbacks, and the annotation shape; each guard mutation-probed red. Pin the dedup label on the list call too (R4-10). * fix(ci): document the version-marker dedup gap on the failure-issue job (#9916) Round 4 removed the unreachable marker-restore branch (R4-9) but left its residual gap undocumented: the dedup lookup only finds the tracked issue while the version marker survives in the body, so a human edit deleting the marker orphans the issue and the next failure files a duplicate. Fold the marker into the job's existing known-gap note alongside its sibling, the scope/ci-cd label (R4-11), and record the workflow's new size. |
||
|
|
38c5f9b4fd
|
fix(ci): yield the event loop between script tests to avoid vitest RPC timeouts (#10037) (#10050)
* fix(ci): yield the event loop between script tests to avoid vitest RPC timeouts (#10037) The v0.22.1 release quality job exited 1 on `npm run test:scripts` with every test green. vitest's worker->main `onTaskUpdate` RPC has a fixed 60s timeout; the synchronous spawnSync-driven script suites keep a forked worker's event loop blocked for an entire file (~66s on the heaviest suite), so the queued RPC response is never processed before the timer fires, surfacing as an unhandled `[vitest-worker]: Timeout calling "onTaskUpdate"` error. Linux keeps unhandled errors fatal (the scripts vitest config only exempts non-Linux since #9728), so the release died. Add a global per-test event-loop yield to the scripts test setup. The timer is captured at setup load so `vi.useFakeTimers()` inside a test cannot intercept the yield. Any continuous stall is now bounded by a single test, so RPC responses drain long before the 60s deadline. Real test failures stay fatal on every platform; the Linux unhandled-error signal is untouched. * fix(ci): state the actual yield invariant in the script test setup comment (#10037) * test(ci): pin the script-test event-loop yield invariant (#10037) --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
0756be0ce7
|
ci: take the macOS and Windows lanes off pull requests (#10059)
* ci: take the macOS and Windows lanes off pull requests
Both lanes were costing contributors more than they were telling them.
Measured over the 18 hours before this change, on pull requests:
- Windows reported 13 failures and 0 successes. The failures are one
standing set of Windows-only path and symlink cases — resolved paths
into read_many_files, releaseWorktree through an ancestor symlink,
the SHA-256 review worktree — repeating across unrelated PRs, so the
red X almost never belonged to the diff under it.
- macOS queued for a p90 of 42 minutes and up to 159, on a hosted pool
this repository does not saturate by itself: 20 of the 63 sampled
waits happened with zero macOS jobs of ours running.
Neither lane gates a merge — the `main` ruleset carries no required
status check — so none of that waiting or noise was buying protection.
Leave them on the nightly, the merge queue and dispatch, and drop the
pull-request arm plus the classifier job that existed only to feed it.
That makes the nightly load-bearing rather than a backstop: it is now
the only report of a non-Linux regression, so add a guard for the three
ways it could go quiet — the schedule disappearing, a lane no longer
accepting it, or a third job joining it and failing the run for reasons
that have nothing to do with either platform.
The classifier, its script mode and both test files are left in place so
restoring the pull-request trigger, once the Windows failures are fixed,
is a revert plus two `if` arms.
* ci: pin the retired-classifier contract in ci-platform-lanes.test.js
The lane change left scripts/tests/ci-platform-lanes.test.js pinning the
shape it removed — a sensitive-PR trigger arm and a live classify_platform
job — which failed the Test job on this branch. Rewrite the suite to pin
the new contract instead: both lanes run on the schedule, the queue and
dispatch and on nothing else; the pull-request arm and the classifier are
gone whole (a half-restoration — a trigger without its classifier, or the
reverse — fails); the classifier's own scripts stay tested so restoring
the trigger stays a clean revert; the nightly still reaches exactly the
two lanes and its failure still files an issue.
That suite already owned the nightly-liveness assertions, so the
platform-lane-triggers.test.mjs guard added earlier on this branch
duplicated it — dropped, along with its HELPER_TESTS entry.
Verified by mutation: deleting the schedule, dropping schedule from one
lane, letting a third job onto the nightly, and reintroducing the
pull-request arm without its classifier each fail the suite; the branch
shape passes 17/17.
|
||
|
|
50c553550e
|
fix(live): restore Live Host after desktop removal (#9994)
* fix(live): restore the standalone Live Host Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(release): restore Live Host release feeds Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * chore(skills): remove the unsupported desktop pet skill * fix(ci): stabilize Live Host validation Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(cli): complete session swap telemetry fixture Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
519e824dc7
|
fix(ci): give the macOS and Windows lanes a trigger again (#9370)
* fix(ci): give the macOS and Windows lanes a trigger again Both lanes are gated on `merge_group`, and no merge queue is enabled on this repository — the `main` ruleset carries only deletion, non-fast-forward and pull_request rules, no status check is required, and merges land as squashes. The last `merge_group` run of anything was 2026-07-02. So the gate was an off switch: the lanes reported as "skipped" on every pull request, which reads as agreement, and nothing ever reached them afterwards. The only signal this repository has about a host that is not Linux with a GNU userland had been silently off for six weeks, which is how #9220 shipped a GNU-only `realpath -m` in a workflow guard with the suite that pinned it red on every Mac. Three triggers now, in cost order. A pull request whose diff a new classifier recognises as platform-sensitive: shell scripts of every dialect, workflow and composite-action YAML and the scripts they call, the script layer and its tests, the test-runner configuration that decides which suites run where, the root manifests, and source paths whose segments name a platform-coupled subsystem. It is a net, not a proof — it cannot see a platform assumption inside an ordinary source file, and no path rule ever will — so every unknown answers "sensitive": an unreadable listing, an unparsable entry, a fork pull request, a truncated file list, or the classify job failing outright all end as "run the lanes". Only a confident `false` skips them. The merge queue, if it is ever enabled again, unchanged. And a nightly run on `main` for everything the path list cannot see. Every other job in the workflow excludes `schedule` explicitly, so a nightly is exactly two jobs, and 'Qwen Code CI' joins the workflows the main-failure watcher opens autofix issues for — a red lane nobody is told about is the same silence the queue gate produced. That watcher gains a trigger-level `branches: ['main']` filter so the CI workflow's pull-request completions do not raise an event there just to skip. The classifier runs in its own small hosted job rather than as a step in `classify_pr`: that job's outputs pick the Linux runner for the whole run, and this one needs a checkout — on a pool whose workspace other jobs have poisoned before. It checks out the pull request's BASE commit, never the head: it runs before any review and executes a script from the tree it checks out. Its listing goes through the existing classify-pr-profile.sh wrapper, extended with a mode argument, because that wrapper's whole point is that one PR is never listed twice and classified differently in two places. Twelve tests pin the wiring — the triggers, the fail-safe direction of the gate, the base-commit checkout, that a nightly stays two jobs, and the alerting — and ten more pin the classifier itself, including the substring traps (`Shellfish.tsx`, `cryptic.ts`, `plateauDetector.ts` must not drag both lanes in) and every fail-safe path. Mutation-checked: flipping the gate to `== 'true'`, dropping the schedule from a lane, dropping the nightly guard from the ubuntu job, pointing the checkout at the head, dropping the classifier's test from the helper list, and removing the schedule trigger are each caught. * fix(ci): gate the Windows lane's checkout verification per trigger The first thing the revived triggers hit was not a test failure but the lane's own plumbing. `test_windows` verifies its checkout with `verify-checkout-head`, and the input was written when this lane ran in the merge queue alone: `expected_sha: github.event.merge_group.head_sha`, with no event gate. On a pull request that expression is empty, the action refuses an empty SHA, and the lane went red in 63 seconds without running a test — the first Windows run in six weeks, failing on the trigger rather than on the code. Give it the event-aware shape the Ubuntu gate already uses, and skip it where there is nothing to verify: the scheduled and dispatch runs check out a branch by name, not a head commit. Pinned generally rather than by name: for both lanes, any step whose inputs read a `github.event.<event>` context must be gated to that event, in the step's own `if` or in the expression itself. Restoring the old spelling turns that test red. * fix(ci): close review round on the revived platform lanes (#9370) * fix(ci): pin the Windows lane routing to the canonical trust policy (#9370) * fix(ci): host-probe the macOS lane's bash and GNU dependencies (#9370) The revived macOS lane ran the shared suite for the first time since the merge queue went dark and failed on exactly three tests, identical across three consecutive runs: the bite-check block and the baseline A/B green path die with `mapfile: command not found` (a bash >= 4.4 builtin; macOS ships 3.2), and the health-probe repair case trips over BSD `mktemp -u` attempting to create where GNU's print-only `-u` just names the canary. Both suites pin scripts that only ever execute on Linux runners — ubuntu-latest or the Linux ECS pool — so the defects cannot exist in production; the suites just cannot run those scripts on a macOS host. Follow the convention #9220's fix established for the realpath case in the same file: probe the host capability, not the platform, and skipIf the three dependent tests where it is absent. A Mac with a newer bash or GNU coreutils fronting PATH keeps the coverage; the Linux lane runs all three unconditionally. The large bite-check test body is re-indented by prettier around the new skipIf wrapper; no other token changed. * fix(ci): stop the subsystem rule matching compounds that name something else The platform-sensitivity classifier split a path segment on dashes and underscores anywhere, so `packages/web-shell/**` matched the `shell` keyword — one of this repository's largest packages, a browser UI with no host coupling, summoning both expensive lanes on every change to it. That is the cost the gate exists to avoid, spent on the wrong diffs. A keyword now counts when it NAMES the thing: a whole path segment (`src/sandbox/**`, `platform/paths.ts`, `shell.ts`) or the head of a hyphen/underscore stem (`pty-host.ts`). Not a trailing part of a compound, which belongs to whatever the leading word names, and still not a substring inside a longer word. Pinned both directions, including a directory that IS named for the subsystem wherever it sits (`web-shell/components/shell/**` stays sensitive). Mutation-checked: dropping either rule, or restoring the split-anywhere spelling, turns the suite red. Also drops a wrong issue citation in the watcher test's comment: the nightly comes from this change, not from the wipe-guard back-port. * test(ci): pin the gate as a disjunction and the watcher's name binding Two mutation-survivable gaps in this PR's own tests. The clause-presence assertions left a connective mutation alive: `||` → `&&` between two event clauses keeps every asserted string in place and makes the gate unsatisfiable for every trigger — both lanes silently off again, which is the state this PR exists to end. Read the event group and require it to be a disjunction, allowing `&&` only inside the pull-request clause that binds to the classifier output. And the watcher's binding to this workflow is by display name: `workflow_run.workflows` matches the watched workflow's `name:`, so renaming ci.yml unhooks the nightly's alerting silently. Pin both sides. Both checked by mutation: flipping one `||` and renaming the workflow each turn a named test red. * fix(ci): close the classifier's CRLF gap and widen the lane step scan Three findings from this round, all in the direction of the tests and the parser being less clever than they claimed. The classifier's JSONL reader split on `\n` while its sibling splits on `/\r?\n/`. Every suffix rule here is end-anchored, so one carriage return on a CRLF listing would leave `build.sh\r` and classify a script-layer change as ordinary source. Matched to the sibling, with a fixture on both the JSON and the raw-line path. The per-trigger step scan serialized only `with:` inputs, so the same defect wearing an `env:` or `run:` key escaped it; it now reads all three. And the nightly blast-radius guard tested for the MENTION of an allowlisted event rather than the IMPOSSIBILITY of `schedule` — a job gated `pull_request || schedule` satisfied it while running every night. It now requires the absence of an explicit schedule clause too. * style(ci): format the runner-routing suite The Windows trust-policy matrix added last round left the file outside prettier's style, which the repository's lint step fails on; main's copy is clean. Formatting only — the nine assertions are unchanged and still pass. * fix(ci): gate the two mapfile-crossing gate tests on the host probe (#9370) * fix(ci): keep pull requests off the persistent Windows pool and cover native audio (#9370) A pull_request run executes the workflow YAML from the PR's own merge commit, so the test_windows runs-on trust clause it evaluated could be rewritten by any PR the lane admits. Every pull request now runs on hosted windows-2022 unconditionally; the pool is reached only by the post-approval merge queue, schedule and dispatch, guarded by the kill-switch. The routing tests and the exact-line pin are re-pointed at that enforceable shape. Also add audio to the platform-sensitivity classifier's subsystem keywords: packages/audio-capture is a node-gyp workspace compiled per-host on exactly the two revived lanes, but its native sources (.cc/.mm/.gyp) carried no rule and a PR touching only them skipped both lanes. The workspace directory now classifies sensitive; an ordinary .cc elsewhere stays ordinary source. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- 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> |
||
|
|
b331509de4
|
fix: repair the Windows and macOS test lane failures (#9728)
* fix: repair the Windows and macOS test lane failures The platform lanes have been dark since 2026-07-02 (gated on a merge queue that is not enabled); reviving them in #9370 exposed these pre-existing failures. 72 failing tests across 16 files, all traced to platform assumptions: Product fixes (2): - daemon-git-worktree-guard: on Windows a backslash is a path separator, not a POSIX escape. The shell-quote tokenizer consumed `\x` pairs, mangling `C:\repo\sub` into a relative word — false denials for legitimate commands AND undetected relocations for backslash-relative ones. Preserve unquoted backslashes before tokenisation on win32. - acpAgent isOwnerOnlyDirectory: hard-returning false on win32 disabled Live managed relocation entirely (Node exposes no ownership bits there). Rest on the structural checks — symlink rejection and dev/ino identity across the realpath round trip — the same trade-off serve/live/discovery.ts already makes. Test-fixture fixes (the product code was already Windows-correct): - server.test Live catalog roots use the host-native path shape (path.resolve equality proof fails for POSIX literals on win32) - review cleanup suite pins POSIX node:path semantics for its literal-keyed mocks; fetch-pr resume budget uses a native tmpdir; scratch-tree clears the DOS read-only attribute before overwriting a git-created gitfile; worktree-list assertions compare slash-normalized (git prints forward slashes on Windows) - mode-bit (0600) assertions skip on win32 (no POSIX permission bits; every read side already skips its mode check there) - O_NOFOLLOW symlink test and the unescapePath no-op test skip on win32; sidecar errno injection uses a portable NUL byte; Footer exact-hint text is platform-conditional (win32 indicator is 8 columns shorter, shifting the flex shrink by one) scripts vitest suite: drop the fixed 8-16 worker floor that oversubscribes the 3-core macOS runners — the main thread stalled past the 60s worker RPC timeout (onTaskUpdate), exiting 1 with every test green. * fix: second round of platform lane repairs from CI verification Verification run (fixes + #9370's workflow) cut the Windows failures from 69 to 5 and left the macOS lane's infra error. Follow-ups: - daemon-git-worktree-guard resolvePhysicalPath: splitting an absolute Windows path yields the drive as a segment (C:), which path.join glued back onto the root as C:\C:. Walk only the part past the root. Exposed by the first round's tokenizer fix, which let intact drive paths reach this code for the first time. - scratch-tree tests: the git-created gitfile refuses in-place overwrite on Windows even after clearing the read-only attribute; delete and recreate instead. - bridge.test stderr audit assertion: the line prints the session id through JSON.stringify, escaping Windows backslashes; match the escaped spelling (test added this morning by #9543, landed after the baseline census). - managed-scratch 'root replaced' test: dev/ino identity is not reliably observable on every Windows volume; state the precondition and skip where the swap is indistinguishable. - scripts vitest suite: the unhandled onTaskUpdate worker RPC timeout is deterministic on the macOS runners with every test green; stop letting unhandled errors fail this suite while test failures stay fatal, and drop the stale claim that the pool override removal fixed it. * fix(ci): skip coverage report generation on non-Linux CI CI consumes coverage only from the ubuntu lane: the artifact upload and the coverage comment both pin coverage-reports-*-ubuntu-latest. On the Windows runners the v8 report generation for 800+ files stalls the vitest main thread past the 60s worker RPC budget at the end of an all-green cli run, exiting the lane 1 (observed in verification run 32569004418). Skip coverage on non-Linux CI; local runs keep it. * fix(ci): stop all-green cli/core runs exiting red on RPC timeout The Windows lane's third verification round repeated the failure with coverage already disabled: 866 cli test files green, then the worker onTaskUpdate RPC budget (60s, hardcoded in vitest's bundled birpc) expired under runner resource pressure and the unhandled error exited the lane 1. Extend the scripts suite's treatment to the two big package suites: test failures stay fatal, unhandled errors do not. * fix: address review on the win32 guard pre-pass and lane configs R1-1 (Critical): the win32 pre-pass escaped the character after every unquoted backslash, so whitespace after a trailing separator glued the next word into the -C value — a second -C/--git-dir/-c parked there vanished from the analysis while cmd.exe still split the argv at the whitespace, allowing a destructive mutation outside the boundary. The tokenizer treats `\<space>` as an escaped space even after an even number of backslashes, so escaping forward can never express "literal backslash, then word boundary". Escape the backslash alone instead: a double-quoted backslash before whitespace and cmd boundary characters (; | & < > ( )) keeps them their separator role, and a plain escaped backslash elsewhere. Verified token boundaries for the attack shape, its tab variant, trailing-separator-before-flag, UNC, chained -C, and quoted paths; added win32-only guard tests for the boundary shapes. R1-2/3/4: gate dangerouslyIgnoreUnhandledErrors to non-Linux — the ubuntu lane and Linux local runs keep the unhandled-error signal. R1-5: pin the deterministic win32 footer truncation ('queu') instead of skipping the content assertion there. R1-6: build the Live conversations fixture root with the file's documented path.resolve(path.sep, ...) convention instead of a hardcoded C: literal. * fix: three Windows lane failures from recent main commits Verification of the revived lanes surfaced three failures introduced by commits that landed while the lanes were dark: - isSameFile compared dev/ino unconditionally; on volumes that report ino 0 (or a colliding value) for every file it equated distinct files. Treat an unverifiable inode like core's hasVerifiableInode convention and fall back to canonical spellings — losing hard-link identity there, but never equating distinct files. The hard-link test skips where the volume exposes no inode. - drive's bound-address recipe test rmSync'd its temp dir while the backgrounded service still held it (EBUSY on Windows); shorten the service's self-exit timer and retry the removal. - the FileReadCache seeding test collided under one dev:ino key when the volume reports the same inode for both MEMORY.md indexes; skip where inode identity is not real. * fix: address round-2 review on identity fail-closed checks and the win32 guard pre-pass * fix(cli): deny cmd.exe rewrite syntax in the daemon git-worktree guard (#9728) * fix: address round-4 review on the cmd-rewrite denial reason and cmd-lane test gating Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix: address round-5 review by closing the divergent Windows shell surface structurally The win32 cmd/PowerShell lanes were analysed through a POSIX text model plus per-stage patches; each round closed one entrance of the divergence family and the next found new ones. Fail closed on syntax whose lane semantics diverge from the model (lone `&`, `( )`, cmd `#`/`;`/single quotes, /s outer-quote strip, PowerShell `--%`/`''` doubling), normalize the whole command text once before any stage reads it, drop the bash shadow model on lanes where the syntax defines nothing, and stop scoping PowerShell pipeline stages as subshells. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix: address round-6 review by gating bash-semantics tests off the win32 lane and failing closed on nested Windows shells R6-1: the whole-text divergent-syntax gate denied ~20 ungated bash-semantics expectations on the real win32/cmd merge lane (41 failures reproduced under a lane-spoof harness). Gate those blocks off the win32 non-bash lanes, splitting mixed blocks so lane-safe pins keep running there, and commit the lane-spoof harness so the whole guard suite runs as the win32/cmd merge lane on every lane. R5-1 (partial): cmd/powershell/pwsh invocations now fail closed on the undecidable-payload denial on every lane — their payloads are parsed by a grammar the POSIX text model cannot read (closes the -EncodedCommand and nested cmd /c entrances probed at this head). The remaining class closure is escalated to the maintainer as a product/scope decision. * fix: address round-7 review by gating the Windows-shell fail-closed rule to the win32 platform R7-1: the round-6 WINDOWS_SHELL_PROGRAMS branch failed closed on every lane, which denied benign cross-platform PowerShell on POSIX daemons (pwsh -NoProfile -Command Write-Output hello reproduced denied on the unresolved reason at this head). Gate the rule to platform win32 — cmd.exe and Windows PowerShell only exist there, and they stay reachable from win32 Git Bash sessions, so the gate is platform-wide rather than windowsNative; on POSIX the same names keep the base stance of ordinary unmodelled programs. Move the fail-closed pins onto spoofed win32 lanes, add a win32 Git Bash lane pin so the entrance stays closed there, and add the POSIX benign-pwsh regression test. * fix(cli): close the cmd.exe state-persisting entrances in the daemon guard Probe-verified bypasses (review R5-1): cmd.exe builtins persist state into every later &&-chained command, and the analysis modelled none of them — `set GIT_WORK_TREE=<outside>&& git reset --hard` and `chdir <outside> && git reset --hard` both analysed cwd-local while the executed command relocated outside the boundary. cmd's state-persisting builtins are a closed set, so this enumerates them instead of chasing individual shapes: - `set VAR=value` / `setx VAR value` carry the semantics of a POSIX `export VAR=value` for every later segment; route them through that machinery (GIT_* keys become relocations, unresolvable keys fail closed). Non-assignment forms (`set /p`, dynamic operands) fail closed. - `chdir` joins the cd family (cmd's synonym), `/D` consumed as the drive-switch option. - `path` and `doskey` rewrite which executable a bare name resolves to — unresolvable, failed closed. - copy/mklink/move/robocopy/xcopy join the relinking programs on win32, and the text relocation markers learn `chdir`. PowerShell-only entrances (function definitions, New-Item function:, @-splatting) already fail closed through the unmodelled-syntax gate; the new branches stay gated off Git-Bash sessions, where these words are ordinary POSIX commands. Pinned by new tests in the win32-lane harness, which spoofs win32/cmd on every platform. * Revert "fix(cli): close the cmd.exe state-persisting entrances in the daemon guard" This reverts commit |
||
|
|
b449a9536a
|
feat(channels): add DingTalk Workspace channel (#9394)
* feat(channels): add DingTalk Workspace channel
Add a DingTalk Workspace (DWS) channel package so a workspace can be
driven from DingTalk alongside the existing channels.
- packages/channels/dws: new workspace holding the DWS client, event
stream, environment resolution and channel implementation, with the
event-source fixtures used by its tests.
- cli: register DWS in the channel registry and its builtin list.
- web-shell: recognise the DWS platform in the channels UI.
- docs: document the channel and its configuration under
docs/users/features/channels.
- build/release: include the new workspace in the build, clean and
release-version scripts and the vitest project list.
The channel watches native DingTalk todos, routes document and todo
replies back to their originating conversation, bounds notification
retries, and keeps sender identity authoritative for direct messages.
* fix(dws): classify spawn-resource errnos as not sent, and test against base's source
Round 1 review, two Critical findings.
vitest.config.ts — the new package's config was the only channel config
without the `@qwen-code/channel-base` → source alias its five siblings carry,
so `cd packages/channels/dws && npx vitest run` (the workflow AGENTS.md
prescribes) depended on a prior `tsc --build` of base. Reproduced the
reviewer's witness in this worktree with base/dist moved aside: without the
alias vitest dies in `packageEntryFailure` and runs zero tests; with it,
63/63 pass. Even when dist exists it may lag base's source — it did here, by
four days.
dws-client.ts — `DWS_NOT_SENT_ERROR_CODES` listed only the path errnos, so a
`dws` process that never started because of fd or memory exhaustion
(`EMFILE`/`ENFILE`/`ENOMEM`/`EAGAIN` and family) was classified `unknown`.
The todo and document reply paths in dws-channel.ts swallow `unknown` as
"the originating task will not be rerun", so a user's final reply was dropped
permanently on one log line instead of being retried — and the retry is safe,
since the fingerprint is not persisted when delivery fails. The set now
carries the whole `uv_spawn` pre-exec family. Everything else the callback
reports — a non-zero exit (numeric `code`), a timeout kill (`code === null`),
`ABORT_ERR`, a `maxBuffer` overrun — happened with a child already running
and stays `unknown`, because a retry there could duplicate a delivery.
The classification moved into an exported `classifyDwsCommandFailure` so the
table can be driven directly: the resource errnos need real fd or memory
exhaustion to reproduce through a spawn, which no unit test can stage safely.
The existing missing-executable test still covers the wiring end to end.
Verified: packages/channels/dws — 191 passed (5 files). Mutation-verified:
reverting the errno set turns exactly the 12 added codes red (12 failed /
51 passed); dropping the vitest alias with base/dist absent turns the suite
from 63 passed into a collection failure. eslint and prettier clean. The one
tsc error on this branch (`displayText` missing from `Envelope`) is worktree
build skew — base/dist was built 2026-08-10, base/src changed 2026-08-14, and
the field is present in the source; it reproduces identically with these
changes stashed.
* fix(dws): stop a denied sender from consuming a document comment's dedup slot
Round-2 review, R2-4 (Critical).
`notificationKey` is `documentNotificationKey(documentId, commentKey)` — no
sender in it — so a `'denied'` outcome falling into the `else` branch marked
that (document, comment) pair processed for good. Every later notification for
the same comment, live or polled, then hit
`processedMessages.includes(notificationKey)` and returned silently, including
one from a sender who IS allowed. The cursor persists, so the drop survived
restarts.
Concretely, with `senderPolicy: 'allowlist'` and `allowedUsers: ['open-bob']`:
Alice (not allowlisted) @-mentions the bot in a document comment and is denied;
Bob then mentions the bot on the same comment thread — the ordinary
multi-reviewer document flow — and is dropped forever, with no dispatch, no
pairing and no log.
A denied notification is now parked with `rememberPendingDocumentNotification`
like a `'pairing'` one rather than consuming the slot. Replay already skips a
pending entry whose sender fails `gate.isAllowed`, so a denied sender does not
get retried in; and an allowed sender reaching the same comment clears the
entry on the way through.
The existing `applies sender access policy to document mention notifications`
cannot cover this — its denied and allowed notifications are on DIFFERENT
comments, so the shared key is never exercised. New test puts both on the same
comment. Mutation-checked: restoring the old condition reddens it with
`bridge.prompt` called 0 times against an expected 1, reproducing the review's
own witness.
Verification: `npm run build` and `tsc --noEmit` clean in packages/channels/dws;
eslint clean on both changed files; full package suite 192/192 (118 in
dws-channel.test.ts, 1 new).
* fix(dws): stop a poison message, a full pending queue, and an unreachable
replay from pinning the watermark (R2-1, R2-2, R2-4 queue)
Three ways history polling could stall forever, each measured:
**R2-2, poison message.** A message whose turn threw was never marked
processed, so the watermark never advanced and every poll re-ran it as a
full agent turn — one model call per iteration, no cap, no backoff —
while the pinned watermark grew the query window without bound and the
throw starved every newer message behind it. Pending-document replay
already had retry accounting; this path had none. Inbound failures are
now counted per message and persisted in the cursor: under budget the
error still propagates (redelivery retry and the concurrent-duplicate
contract depend on that, and their tests pin it), and once the budget is
spent the message is marked processed and dropped with a logged reason.
**Pending-queue cap.** `rememberPendingDocumentNotification` threw at
MAX_PROCESSED_ITEMS, and the throw aborted the direct-message loop
before the checkpoint, the watermark and `markProcessedMessage` — so
every later poll re-scanned a growing window and re-threw on the same
never-marked message, surviving restarts in the cursor. The queue's only
drain is an allowed sender later processing the same comment, so entries
parked for unapproved senders never leave: one unpaired member
@-mentioning the bot in 5,000 distinct comments broke document history
polling until manual cursor surgery. It now evicts the oldest instead,
which costs at most a pairing prompt nobody approved.
**R2-1, the replay the fixture could not recover.** The test fake
ignored its `startTime`/`endTime`, so it certified a recovery the
production arithmetic cannot perform. Fixed on both sides: the fake now
filters by its window like the real client (and `message()` defaults
`eventTime` to now, since real messages always carry one — six fixtures
were silently relying on epoch 0), and the stale-replay guard now pulls
`notificationWatermark` back to the parked notification's event time. It
parks document notifications UNMARKED on purpose, "for polling to
recover"; on a fresh cursor the watermark started at
`connectionStartedAt` and the window opened at `watermark − 5s` —
exactly the guard's own drop boundary — so everything it parked was
strictly outside every window that watermark would ever produce.
Every fix is mutation-verified: reverting the retry budget re-runs the
poison turn once per poll (8 polls, 8 turns), restoring the queue throw
reproduces the reviewer's stderr and the pinned watermark, and dropping
the watermark pull-back leaves the replayed notification unrecovered.
Suite 194/194 green; `tsc -p packages/channels/dws` clean.
R1-2 (self-identity degradation) is not in this commit — both fixes the
review proposes collide with behaviour this suite pins deliberately; see
the thread.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(dws): budget every inbound surface, and stop restarting a dead source (R4-1, R4-3)
R4-1: round 3 added an inbound failure budget, but wired it into one of the
three `handleInbound` call sites — the mention/live-IM path. The other two kept
the exact unbounded-retry mode the budget's own doc comment says it exists to
close.
- Document notifications (`processDocumentNotification`): a throw escapes
`pollOnce`'s sorted loop and is swallowed by the outer catch, so nothing is
marked processed and `notificationCheckpoint`/`notificationWatermark` — both
assigned after the loop — never advance. Every 5s poll re-ran the same full
agent turn, forever, starving every newer notification behind it.
- Native todos (`pollTodos`): the fingerprint is remembered only on success, so
a todo whose turn keeps throwing was re-fetched and re-run every poll,
forever.
`recordInboundFailure` now takes the drop action as a parameter, because "stop
re-running this" differs per surface: marking the key processed is right for a
message, a document notification carries its own `notificationKey` (and a
pending entry to clear), and a todo is re-fetched by fingerprint. The default
keeps the mention path byte-identical.
R4-3: `retryable: false` is terminal before ready — `retryLimit` returns 0 —
but `scheduleImRestart` never consulted it, and `startImSource` resets
`restartAttempts` to 0 every time a subscription becomes ready. The backoff
exponent therefore stayed at 0, so a permanently denied consumer (permission
revoked, subscription not allowed) was respawned at a constant ~3s forever —
one `dws event consume` child every 2-3s per affected source — while the
channel reported itself connected and delivered nothing for that source.
Post-ready now matches pre-ready: terminal, with a log line saying so.
Verification (`cd packages/channels/dws`):
- `npx vitest run` — 197 passed (was 194; three new tests).
- `npx tsc -p tsconfig.json --noEmit` — clean.
- Mutation checks, one per fix, each turning exactly its own test red and
leaving the other 122 green:
- drop the `retryable === false` guard -> `stops restarting a source that
died permanently after becoming ready` fails.
- drop the document-path budget -> `drops a document notification whose turn
keeps failing, and stops starving newer ones` fails (the newer
notification is never reached).
- drop the todo-path budget -> `drops a native todo whose turn keeps
failing` fails (8 turns instead of 5).
- eslint + prettier clean.
Not addressed in this commit: R4-2 (checkpoint drain overwriting the stale
replay pull-back), R4-4, R1-2, and R4-5..R4-8.
* fix(dws): stop an in-flight poll from clobbering the stale-replay pullback (R4-4)
`handleImMessage` leaves a replayed document notification UNMARKED on purpose,
for history polling to pick up, and pulls `notificationWatermark` back to the
replay's `eventTime` so a future window can reach it. `pollOnce` then wrote
`checkpoint.endTime` over that watermark unconditionally when its own window
finished — and `checkpoint.endTime` is always past the replay's `eventTime`.
The race is not hairline: `runLoop` polls immediately on connect and the IM
subscriptions start before the poll loop, so a startup replay arrives precisely
while poll #1's `listDirectMessages` is awaiting. One clobber puts the parked
replay outside every window the watermark will ever produce — no turn, no log,
no error, and it survives restarts because `saveCursor()` persists it.
`pollOnce` now records whether the watermark was pulled back while its
direct-message fetch was in flight, and on that path drops the window instead of
finishing it: neither the advance nor the paginated checkpoint resume is safe,
because the checkpoint was itself derived from the pre-pullback watermark. The
next poll re-derives a window from the pulled-back value.
Test: `keeps the stale-replay pullback when a poll was already in flight` emits
the replay from inside `listDirectMessages`. Mutation-checked — forcing the
guard false reddens it with `inbound` empty, matching the reviewer's witness
(`dispatched = 0`). It also asserts the second query window opens at or before
the replay's `eventTime`, so a fake that ignored its window could not certify it.
* fix(dws): stop three silent, permanent losses of a document mention (R6-1/R6-2/R6-3)
All three Criticals round 6 raised share a failure shape: a document comment is
consumed by something that had no right to consume it, the user gets no reply,
and nothing is logged. Each is fixed at the point that consumes the slot.
R6-1 — `handleImMessage` pullback (dws-channel.ts): R4-4 rescued a stale replay
by pulling the notification watermark back, but the flag `pollOnce` consults is
cleared at the top of every fetch, so it only ever covered a replay that landed
DURING one. A pullback arriving in the gap between two polls is reset before it
is read; a persisted multi-page `notificationCheckpoint` then resumes a window
that starts after the replay and finishes by writing `checkpoint.endTime` back
over the pulled-back watermark. The replay was left unmarked on purpose, so
after that no window ever reaches it again. The pullback branch now drops the
checkpoint as well, which makes the rescue durable regardless of when the
replay arrived; the in-flight flag still guards the during-a-fetch case.
R6-2 — in-flight awaiter (dws-channel.ts): a pending entry means the in-flight
turn PARKED the comment for a sender it would not serve, which says nothing
about the caller waiting behind it. Marking unconditionally consumed an ALLOWED
sender's mention outright — replay only re-drives a parked entry whose own
`senderId` passes the gate (the denied one never will), and the allowed
sender's marked message key is skipped by every later history poll. The awaiter
now marks only when the comment is genuinely processed, or when this caller is
no more entitled to it than the sender already parked. This is what the
denied-sender comment further down already claimed happened ("an allowed sender
reaching the same comment clears the entry on the way through") — the awaiter
was the path that never let them reach it.
R6-3 — failure-budget drop closure (dws-channel.ts): the closure marked the
sender-agnostic `notificationKey` (`document\0comment`, no sender), so five
failed turns — about 25s of transient model or bridge trouble, since each 5s
poll re-runs an unmarked notification — dropped every FUTURE mention of that
comment from anyone, permanently and across restarts. It now marks only the
failing message's own `key`, which is what stops the window re-running it, so
the R4-1 starvation this budget closes stays closed.
Tests (dws-channel.test.ts), each mutation-verified against the pre-fix code:
- `keeps a stale-replay pullback that arrives between two polls` — persists a
bounded checkpoint, emits the replay with no poll in flight, asserts the
checkpoint is released and the next window reaches back over the replay.
Reverting R6-1: `expected { startTime: … } to be undefined`.
- `lets an allowed sender through while a denied turn on the same comment is in
flight` — the concurrent counterpart to the existing R2-4 test, which lets
the denied turn finish first and so cannot reach the awaiter. Reverting R6-2:
the allowed sender's prompt is never called.
- `lets a later mention of a dropped comment retry with a fresh budget` — five
failing polls, then a different reviewer on the same comment after the
outage. Reverting R6-3: `expected [] to deeply equal [ ObjectContaining{…} ]`.
Verification: `npx vitest run` in packages/channels/dws — 201 passed (5 files);
`npx tsc --noEmit -p packages/channels/dws/tsconfig.json` clean; `npm run build`
in that package clean; eslint and prettier clean on both changed files.
R1-2 is untouched: it still needs a maintainer call on which pinned contract
gives, and is not something this commit should decide.
* fix(dws): resolve the sender gate before reading a mentioned document (R7-1)
`parseDocumentMentionNotification` reconstructs `(documentId, commentKey)`
from rendered message text, so a bare alidocs URL in an ordinary DM forges a
mention card the channel cannot tell apart from a genuine platform
notification. `processDocumentNotification` then called
`readDocumentContext` on that attacker-named document BEFORE `handleInbound`
resolved the sender gate, so under the documented default
`senderPolicy: 'pairing'` an unpaired stranger could force this profile to
perform an authenticated read of any document it can reach — a turn the
channel would never serve them.
Resolve `gate.isAllowed(message.senderId)` first and read only for a sender
this channel will actually answer. The envelope already carries a "Document
Markdown was unavailable" fallback, the `preflightInbound` document branch
still parks the mention exactly as before, and
`replayPendingDocumentNotifications` re-enters this path once the sender is
approved, so an approved turn still gets its document context — just after
the gate instead of before it.
BEHAVIOR FLIP: `replays a pairing-pending document mention after approval`
pinned `readDocument` being called once for the still-unpaired sender and
twice overall. That pinned expectation was the defect: it asserted an
authenticated read driven by a sender the gate had already refused. It now
expects zero reads before approval and one after. Verified by mutation —
reverting the guard turns both this test and the new forged-mention test red.
Still open on this class and NOT addressed here: the pairing-code write into
the attacker-named comment thread. Closing that needs either fail-closed
verification that `commentKey` is a real comment on `documentId` mentioning
this profile (no DWS CLI surface exposes it — `listMentionedMessages` covers
group IM, not document comments) or structured mention events, so it is a
maintainer contract call rather than a local fix.
Verification:
- packages/channels/dws: 202 passed (5 files), including the new
`does not read a forged document mention before the sender gate resolves`
- tsc --noEmit -p packages/channels/dws/tsconfig.json: clean
- eslint + prettier --check on both changed files: clean
* fix(dws): list the dws channel as a cli test build prerequisite
`channel-registry.ts` dynamically imports `@qwen-code/channel-dws`, whose
package.json resolves the bare specifier to `dist/index.js` and which
`packages/cli/vitest.config.ts` does not alias to source. It therefore
belongs in `DIST_PREREQUISITES['packages/cli']` alongside every other
builtin channel, so a cli test run on an unbuilt checkout reports the
actionable "run npm run build" message instead of a raw resolution error.
This is what the required `Test (ubuntu-latest, Node 22.x)` check caught
on 4bf040766c: scripts/tests/vitest-global-setup.test.js asserts the list
stays in sync with the registry, and dws was the one registry import
missing from it.
Verified: `npx vitest run scripts/tests/vitest-global-setup.test.js`
29 passed; reverting this one line reproduces the CI assertion exactly
("missing prerequisite entry for packages/channels/dws"), 1 failed | 28
passed. prettier --check and eslint clean.
* fix(dws): close current review blockers
* test(dws): pin fail-closed self identity gate
* fix(dws): preserve retryable inbound work
* fix(dws): preserve in-flight catch-up mentions
* fix(dws): align channel-base on the workspace version so npm ci resolves
`Dependency CVE audit` has failed every run with:
npm ci can only install packages when your package.json and
package-lock.json are in sync.
Missing: @qwen-code/channel-base@0.21.11 from lock file
The diagnosis of "stale base" was right, but the stale file is this PR's
own. `packages/channels/dws` was written when the workspace was at
0.21.11 and pins that version; every sibling channel — dingtalk, feishu,
github, gitlab, qqbot, telegram, wecom, weixin — now says 0.21.14, which
is what `packages/channels/base` actually publishes. A workspace package
cannot satisfy 0.21.11, so npm resolved `@qwen-code/channel-base` for dws
from the REGISTRY instead of linking the sibling, leaving a nested
`packages/channels/dws/node_modules/@qwen-code/channel-base` entry that
`npm ci` refuses. Merging current main cannot fix it: main is not where
the pin lives.
Bump dws to 0.21.14 for both its own version and its channel-base
dependency, matching every sibling, and regenerate the lockfile. The
nested registry entry is gone and dws now links the workspace like the
others. `npm ci --dry-run` completes, and dws typechecks and passes all
211 tests against the workspace channel-base rather than the published
0.21.11 it was resolving before.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VgTjRF91xANQh6SY9YGyCf
* fix(dws): replay failed direct messages, and close open review items
* fix(deps): bump tar to 7.5.22 to unblock CVE audit (#9394)
The 2026-08-21 advisory GHSA-r292-9mhp-454m flags tar <= 7.5.20 as
high severity, failing the Dependency CVE audit gate. Main already
moved to 7.5.22 in #9703, but that landed after this branch's last
merge of main. Bump the lockfile entry in-range (core/cli declare
^7.5.19) to match main, and regenerate the committed NOTICES.txt
artifact whose freshness is enforced by CI.
* fix(dws): unblock npm ci, add publish metadata, and keep todo fetch failures out of the turn budget (R13-1, R13-2, R14-1)
* fix(dws): dedup threaded pairing comments on a persisted marker instead of the rotating code (R15-1)
* fix(dws): clear the todo pairing marker when pairing resolves, not on turn success (R16-1)
* fix(dws): address current review blockers
* fix(dws): satisfy event fixture lint
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
|
||
|
|
ce72ddbe6c
|
feat(desktop): remove packages/desktop after OpenWork fork; keep the Tauri upgrade bridge (#9085)
OpenWork (modelstudioai/openwork) has forked the Electron desktop code and is self-contained now, so retire the Electron package and its release/sync machinery from this repo: - Delete packages/desktop (Electron app, live-host app, bun workspace). - Retire scripts/desktop-openwork-sync.ts and the desktop-openwork-sync root script; the OpenWork sync is no longer needed. - Retire .github/workflows/live-host.yml, live-host-release.yml and sync-live-host-to-oss.yml; live-host releases now live in OpenWork. The CLI-side packages/cli/src/serve/live code stays for now (separate cleanup). - Retire scripts/check-voice-guard-sync.js (cli<->desktop parity only) and its CI step. - Clean up remaining references: root package.json workspaces negation and package-lock.json, eslint/prettier/yamllint ignores, architecture docs, web-shell skill descriptions, and review-lib workspace fixtures/comments (renamed to point at packages/desktop-shell, the remaining negation). Deliberately kept: the Electron->Tauri upgrade bridge — desktop-release.yml (incl. the electron_bridge input), create-electron-bridge-manifest.mjs, sync-desktop-to-oss.yml (mirrors Tauri desktop-shell artifacts only) and everything under packages/desktop-shell. |
||
|
|
e89a32c18c
|
fix(ci): neutralize legacy ##[ commands in autofix stdout echoes (#9871)
* fix(ci): neutralize legacy ##[ commands in autofix stdout echoes A review finding about workflow-command injection carried its payload strings (::error::forged, ##[add-matcher]) verbatim as example text. Prepare echoed feedback.md raw, the runner parsed the mid-line ##[add-matcher] and failed the step before the agent ran, and the consecutive-failure breaker burned the takeover window of #9761 in about 70 minutes while reporting it as a 100/100 round cap. The existing neutralization covered only the modern :: syntax; the runner also parses the legacy ##[name] form, even mid-line. Extend every untrusted-content echo in the autofix family (prepare feedback echo, both artifact dump loops, the gate's failure/handoff/no-action echoes, the deferred-findings dumps and upsert re-emit loops) to neutralize both prefixes, and pin the census plus a behavioral ##[ case in the contract tests. * fix(ci): unify the autofix neutralizer on one canonical spelling Review feedback on #9871: the two upsert re-emit loops neutralized with a hybrid spelling (bash expansion for :: plus a single-syntax sed for ##[) while the other fourteen sites used the canonical two-expression sed, establishing two implementations of one normalization. A future edit fixing one spelling could miss the other and recreate the single-syntax half-guard that let #9761 through. Unify both loops on the canonical sed and consolidate the pinned test shapes. The census test's comment also promised more than its assertions deliver: it pins the known enumeration, it does not auto-detect new un-neutralized echoes. Reword it to state the actual guarantee, ban the ##[-only sed and the bare bash expansion alongside the existing ::-only ban, and fold the script-side re-emit loop into the census. * test(ci): assert the neutralizer pairing property instead of banning shapes The three shape bans only matched the no--e spellings, so the canonical line's natural half-copies (sed -e 's/::/;;/g' alone, or the ##[-only -e mirror) passed every ban while guarding only one syntax. Assert the pairing property instead: every occurrence of either substitution expression must belong to the canonical two-expression pair, so a half-guard in ANY spelling unbalances the count and fails the census — verified by probe against all four half-guard shapes. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
74ca981bce
|
fix(ci): scope workflow-size ratchet to the PR that grew the file (#9931)
* fix(ci): scope workflow-size ratchet to the PR that grew the file (#9904) A workflow that grew on main without a same-PR baseline bump red-walled every other open PR's CI (recurred twice in two weeks). Given the PR's base commit, the growth branch now downgrades to a warning when the PR's copy of the file is byte-identical to the base; a PR that actually changes the file still fails closed, as does any unresolvable base. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(ci): single-quote the base-sha env value for yamllint The repo's yamllint config requires single-quoted strings; the value contains no single quotes, so the double-quoted form failed the quoted-strings rule in CI. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(ci): align workflow-size ratchet mirror with the PR-scope downgrade (#9931) Address review round 1: - Single-quote the WORKFLOW_SIZE_BASE_SHA value; the double quotes violated yamllint's quoted-strings rule and prettier's singleQuote, hard-failing this PR's own Test lane. - Wire the base SHA into every `npm run test:ci` step and grant the vitest mirror the same stale-baseline leniency as the shell gate; the mirror is the only enforcer on the merge-queue lanes, and without this the red wall just moves from the gate into test:ci. - Apply the downgrade to the missing-entry arm too (same red-wall shape as a stale size). - Return a distinct status for an unresolvable base and add a diagnostic, so a transient fetch failure is not annotated like genuine PR growth. - Qualify the success banner when stale-baseline warnings fired. - Deduplicate the hermetic-env construction, pin the brand-new-file class, and split the git fixtures into their own describe so the strict-path tests still run on git-less runners. * fix(ci): close review gaps in the workflow-size ratchet mirror (#9931) - Apply the #9904 stale-baseline leniency to the mirror's entry test, not only the allowance test: a workflow that reached main without a baseline entry used to warn in the bash gate but hard-fail every unrelated PR in `npm run test:ci`, relocating the exact red wall this PR removes. - Distinguish an unresolvable base from a changed file: fileMatchesBase now throws with "re-run the job" guidance instead of folding into false behind a message blaming the PR's growth, and the fetch stderr reaches the log. - Hoist WORKFLOW_SIZE_BASE_SHA to the workflow-level env block so every lane inherits it; delete the four hand-wired step-level copies and point the tripwire test at the single source. - Bump the ci.yml baseline entry to the file's true post-hoist size. - Harden the mirror's witnesses: fixtures hoisted out of the bash-gated block so they run on the merge-group Windows/macOS lanes, a backslash-path case pinning the pathspec normalization on every lane, a shallow-clone fixture pinning the mirror's fetch arm (the production path), and a parity fixture running the bash gate and the JS mirror on the same repo state. * fix(ci): address round-3 review on the workflow-size ratchet (#9931) --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
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> |
||
|
|
be891657f7
|
refactor(node-repl)!: deliver the persistent Node REPL as a standalone MCP server (#9499)
* feat(core): add persistent Node REPL runtime * fix(core): align Node REPL phase-one contract * fix(core): align Node REPL module compatibility * fix(core): remove unused Node REPL host broker * refactor(node-repl)!: deliver as a standalone MCP server, revert core tools Replaces the three built-in `packages/core` node_repl tools with a self-contained MCP server package, `@qwen-code/node-repl-mcp`. Why --- Issue #9333 triage accepted the runtime only "for exploration" and gated it on an open maintainer decision: built-in core tool vs MCP-server-first. Reversing OpenAI Codex 0.149.0 settled the shape — it ships code mode as a standalone host with an in-process fallback, and its real-Node `js_repl` (not the restricted V8 `exec`) is the analogue this roadmap needs, because stage 3 (#9335) imports cua-driver's N-API addons, which only real Node can load. Delivering out-of-core also keeps a security-relevant subsystem out of the maintainer-gated `packages/core` until its value is proven. What changed ------------ - New `packages/node-repl`: the kernel, module loader, cell transform, protocol and kernel manager, ported and now dependency-free (local `debug-log`/`win-path`/`tokenizer` replace core utils; `output-adapter` emits MCP content blocks in place of `result-converter`). - Reverted every `packages/core` change, the ~11 packaging files that existed only to ship the runtime assets into six distribution layouts, and the two design/plan docs describing the core delivery. - Deleted the trusted-package/sha256 layer: it was empty and unreachable in production (`module-loader.mjs` 882 -> 483 lines). - Wired the package into `scripts/build.js` and the root `vitest.config.ts`. Net effect: `packages/core` is untouched relative to main; the tool is opt-in via `mcpServers` instead of registered unconditionally for every user. Correctness fixes made while porting ------------------------------------ - Stack line numbers were wrong and drifted with binding count (source line 4 reported as 87). The prelude is now one physical line and the cell compiles with `lineOffset: -1`. - Top-level `var` nested in blocks/try/switch/loops was silently dropped; collection now walks the statement subtree, pruning at function boundaries. Verified against real Node for 16 constructs. - A binding named `nodeRepl` permanently broke the output channel; such cells are now rejected. Ordinary globals stay shadowable, matching plain Node. - Errors thrown by imported modules lost their class, `code` and stack. - A throwing frame handler could discard buffered protocol frames. - A hoisted `var` assigned before a throw is now kept, as Node does. - Unhandled rejections settling after a cell no longer vanish. - Live sandbox timers are capped, so one runaway loop cannot saturate the session's event loop. - Image MIME types are matched case-insensitively on both input paths. - Binding sort no longer depends on host locale collation. - The published bin lacked a shebang and mis-detected its entry point, and the server plus its kernel leaked on every host disconnect. Tests: 146 across 14 files, including a compiled N-API addon fixture, 100 consecutive cells, 10 concurrent isolated kernels, stack-line fidelity, and hoisting semantics. Three smoke scripts cover the adapter, the MCP wire and process lifecycle; the packed tarball was installed into a clean project and driven end to end. Refs #9333 * fix(node-repl): pin zod to the hoisted 3.x so the workspace build type-checks `packages/node-repl` declared `zod ^4.1.13`, so `npm ci` installed a nested zod 4.4.3 for it while `@modelcontextprotocol/sdk` resolved the hoisted zod 3.25.76. Two zod type identities in one compilation made every `registerTool` input schema unassignable (`Type 'ZodString' is not assignable to type 'AnySchema'`), failing `npm run build` for this workspace — and with it the install step of every CI job that builds workspaces. The SDK accepts `^3.25 || ^4.0`, so pin the hoisted 3.x line and drop the now-stale nested lockfile entry. One deduped zod, no behaviour change. This only reproduced with a lockfile-driven install; the local tree had no nested copy, which is why the build passed locally and failed in CI. * fix(lint): lint package .mjs node scripts with the node env The eslint "scripts we run with node" override matched `packages/*/scripts/**/*.js` but not `.mjs`, nor a package-root `build.mjs`. `packages/node-repl` is `type: module`, so its `build.mjs` and `scripts/*.mjs` were linted as browser code and `eslint .` failed with `'process' is not defined` / `'console' is not defined` / `'setTimeout' is not defined`. The pre-commit hook only lints `*.{js,jsx,ts,tsx}`, so `.mjs` files are not checked locally — this only surfaced in the root CI lint step. Add `packages/*/scripts/**/*.mjs` and `packages/*/build.mjs` to the override, matching the existing `.js` entries. Generic, additive, no behaviour change. * fix(node-repl): address round-3 review findings (resolution, error identity, image bound) Triaged the bot's round-3 critical findings against the ported code and fixed the three that were genuine defects here; each has a regression test. - R3-5 (resolution): a symlinked `<cwd>/node_modules` — the norm under pnpm, monorepo hoisting and shared CI caches — was silently dropped, so the documented zero-config `await import('pkg')` failed with "cannot resolve from 0 module roots" while plain Node resolved it. The implicit cwd root was applying a re-link self-comparison that only makes sense for registered roots (which carry a registration-time canonical baseline). Follow the symlink for the implicit root, but skip it entirely when the cwd node_modules is itself a registered root, so the registered root's revocation guard is not undermined. - R3-3 (error identity): an error thrown by a host builtin inside imported code (e.g. `fs.readFileSync` → ENOENT) was rewrapped message-only, dropping `code`/`errno`/`syscall`/`cause`/`stack` — so `catch (e) { if (e.code === 'ENOENT') }`, a ubiquitous Node idiom, silently took the wrong branch. Carry those fields onto the realm-wrapped error. - R3-6 (image bound): image-frame `mimeType` was unbounded — only `data.length` counted against the raw image budget — so a malformed/forged frame could retain a huge string that also got interpolated into a notice and tokenized. Bound the MIME at frame ingestion (a real image MIME is a few dozen chars). - R3-4 is by design (the runtime is not a security boundary), but the tool description recommended `createRequire` without noting it is not subject to the process denial or module-root containment the import path enforces; the description now says so. - R3-1 / R3-2 (the `.mjs` lint failure) were already fixed in an earlier commit. Suite: 148 tests (added symlinked-cwd resolution and host-builtin error-code regressions). The symlink fix initially defeated the registered-root revocation test; the registered-root deferral above resolves both. * docs(node-repl): note top-level function/class re-declaration needs a fresh name Round-3 review R3-12/R3-33: re-declaring an existing top-level function or class in a later cell is a link-time SyntaxError (they persist as `let`, which the prelude re-declares), whereas a plain Node REPL accepts it. A correct fix needs declaration-kind tracking the manager does not carry today; until then the tool description's rerun guidance ('prefer var') is corrected, since a function cannot be made rerunnable via var. --------- Co-authored-by: Claude <noreply@anthropic.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> |