mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-28 19:13:36 +00:00
661 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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> |
||
|
|
0baaec2b32
|
chore(ci): Drop NPM_TOKEN in favor of npm Trusted Publishing (#9552)
* chore(ci): Drop NPM_TOKEN in favor of npm Trusted Publishing * chore(ci): Pin npm 11 for Trusted Publishing in release jobs * test(ci): Cover Trusted Publishing requirements |
||
|
|
48b30647d0
|
refactor: centralize cross-package contracts (#9497)
* refactor: centralize cross-package contracts * fix(build): harden cross-package contract checks * docs(core): clarify sub-session prompt limit scope |
||
|
|
b219e3a716
|
chore(ci): Add --provenance to npm publish and id-token permission (#9532)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
Security Checks / Dependency CVE audit (push) Waiting to run
Security Checks / Secret scan (TruffleHog) (push) Waiting to run
* chore(ci): Add --provenance to npm publish and id-token permission * test(scripts): expect --provenance in npm publish step assertion PR #9532 adds --provenance to every npm publish in the release pipeline. Update the workflow-pinning test to match the new command so the helper test suite stays green. |
||
|
|
3b3818db87
|
fix(ci): keep qwen-autofix.yml under GitHub's 500 KB start-runs limit (#9517)
GitHub does not start runs for a workflow file larger than 500 KB (512,000 bytes) and reports nothing when it stops. qwen-autofix.yml crossed that line on 2026-08-19 at 512,782 bytes: schedule ticks stopped firing, every workflow_dispatch sat "queued" forever with zero jobs and could not be cancelled, and issues/issue_comment went quiet — while pull_request_review runs kept succeeding, because a PR event resolves the workflow from the PR's own branch and those carry older, smaller copies of this file. The loop therefore looked half-alive and stayed dark for a day. Move 75 long comment blocks (1,326 lines) verbatim into a sibling design record, .github/workflows/qwen-autofix.md, leaving each block's opening lines plus a `qwen-autofix.md#af-NNN` pointer where it sat: 518,055 -> 426,437 bytes. No executable line changes — the YAML parses to an identical document outside `run:`, every `run:` script still passes `bash -n`, and the only lines removed anywhere are comments. Steps that are duplicated verbatim across jobs share one pointer so they stay byte-identical. Add .github/scripts/check-workflow-size.sh (gate at 470,000 bytes), wired into CI on every profile: a .github-only PR classifies as `github_ci_only` and skips the `full`-only checks, which is exactly the PR that can trip this. Tests pin the gate, every workflow's size, and pointer/section symmetry. Delete qwen-autofix-recovery.yml. It was cloned during the incident on the theory that the workflow ENTITY was wedged, but it carried the same oversized file, so its dispatches queued identically and its schedule never fired. |
||
|
|
133cf8bfcf
|
refactor(cli): consolidate shared helpers ahead of the legacy audit skill (#9345)
* refactor(cli): consolidate shared helpers ahead of the legacy audit skill
Move the pieces the upcoming /audit skill needs out of command-group
ownership so no skill imports across command groups:
- the findings schema moves from commands/review/ to cli/src/utils/ as-is;
every review consumer imports it from the new home, and the stale-bundle
digest, bundle-asset list, and artifact comment track the move
- safeTarget (traversal-safe slug) and tokenizeArgs (quoted argument
splitting) lift to cli/src/utils/paths.ts and shell-args.ts, with
review's copies re-exporting/redirecting
- the two private git check-ignore copies (review test-plan, team memory)
consolidate into one fresh-by-default helper in core utils; the memo
stays caller-side so a remedy re-check observes the flip
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(cli): restore review ledger machinery dropped by the consolidation
The shared-helper consolidation silently reverted three behaviors PR #9175
added to compose-review, against the PR's stated "no behavior change"
intent: the unreviewed-dimension anchor exemption (scopeUnproven /
dimensionGapsAreDepthOnly / isNonDiffDimensionGap), the LEDGER_MAX_ROUND
stamp clamp, and the bilingual budget-stop phrase splice. Restore them with
the tests that pin them; SKILL.md, ledger.ts, and deadline.ts still
document all three.
Also harden the new helper tests:
- safeTarget: the deep-path fixtures now share a flattened prefix longer
than the kept window, so a truncation-only slug (no digest) collides
instead of shipping green.
- isGitIgnored: each GIT_* scrub arm now carries a discriminating fixture
(three arms previously passed with their scrub line deleted), and every
foreign git init scrubs ambient repo-placement selectors.
* fix(core): scrub git config-injection channels from the ignore probe
Ambient GIT_CONFIG_COUNT (inline KEY/VALUE injection) and
GIT_CONFIG_GLOBAL/GIT_CONFIG_SYSTEM (config-file redirects) can aim
core.excludesFile at a foreign rules file and flip the probe's verdict
for the -C worktree — the same leak class the existing selector scrubs
close. Measured on the pristine probe: both channels turn a
not-ignored path into an ignored one.
Also pin the guards the review found unpinned: the two config
channels, the `--` separator for dash-leading paths, the timeoutMs
wiring, safeTarget's hash-of-original-target property, and
tree-existence of the lifted review helpers.
* fix(core): scrub pathspec-magic env channels from the ignore probe
* fix(core): drop the whole GIT_* env family from the ignore probe
Two more leak channels surfaced on the probe's per-variable scrub list:
GIT_ICASE_PATHSPECS (the fourth pathspec modifier — ambient, makes
check-ignore reject every pathspec with exit 128, which the catch reads
as not-ignored) and GIT_CONFIG_PARAMETERS (the inline -c channel git
itself uses to propagate config to children — ambient, can aim
core.excludesFile at a foreign rules file). Measured through the real
function: the first flips a genuinely ignored path to not-ignored, the
second flips a not-ignored path to ignored.
Since the channel list grew by one leak per review round, drop the
whole GIT_* family instead of enumerating, and close the system config
tier explicitly (GIT_CONFIG_NOSYSTEM=1) so host policy in
/etc/gitconfig can no longer answer for the -C worktree — that ambient
dependency also made the config-redirect arm red on any host whose
system config matches the probe path. Pin the icase member, the
PARAMETERS channel, the default 5 s deadline (previously unpinned), and
add a lower timing bound to the caller-deadline arm so it cannot pass
vacuously when the shim is not executable.
* fix(cli): make the safeTarget slug space prefix-free at the dash boundary
Review's cleanup sweeps .qwen/tmp/ by qwen-review-<slug>- prefix. A slug
that itself carried '-' — natively (pr-6771 vs pr) or via the truncation
join — could extend a shorter slug, letting one target's cleanup delete a
DISTINCT target's artifacts (R8-7). The truncation branch this PR carries
newly lands deep targets inside the cap instead of dying ENAMETOOLONG,
which turned the latent collision live.
Drop '-' from the slug alphabet entirely (dashes flatten like separators)
and join the truncation digest with '_': with '-' out of every slug, the
qwen-review-<slug>- boundary is unambiguous by construction — no slug can
start with another slug plus '-'. prev-ledger side files keep their
hardcoded dashed name on both writer and reader, untouched by the slug.
Tests pin the prefix-free property (short-vs-short, short-vs-truncated)
and the fixture names follow the new slugs.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* revert(cli): restore safeTarget byte-identical to the pre-lift behavior
Reverts the slug alphabet change (
|
||
|
|
b5577b7d11
|
fix(sdk): route unrecognized diagnostics onto a bounded transcript sidechannel (#9202)
* fix(sdk): route unrecognized diagnostics onto a bounded transcript sidechannel
Normalizer-classified unrecognized_event / unrecognized_session_update debug events no longer enter transcript blocks[]: they are mirrored onto a capped unrecognizedDiagnostics sidechannel instead. This stops them from finalizing a streaming assistant/thought block (which dropped a following assistant.usage frame) and from consuming the maxBlocks budget (which let repeated noise evict real conversation content). malformed_payload diagnostics and client-dispatched debug events keep their existing block semantics.
* fix(sdk): align browser bundle budget
* fix(sdk): close the sidechannel review round (#8823)
- export the sidechannel API through the daemon barrel
(selectUnrecognizedDiagnostics, UNRECOGNIZED_DIAGNOSTICS_LIMIT,
DAEMON_UI_UNRECOGNIZED_DIAGNOSTIC_REASONS + types) and pin the
reachability in daemon-public-surface.test.ts
- restore the MAX_TEXT_BLOCK_LENGTH cap on sidechannel text, mirroring
truncateText exactly (suffix fits within the cap)
- ship the unrecognized reason subset as a runtime const array and route
by membership, so a new reason cannot fall through to appendStatusBlock
- copy the correlation fields createBase stamps (promptId, sourceRecordIds,
branchRecordId, originatorClientId) onto sidechannel entries; drop the
dead source/data switches
- un-fuse the budget-history comment chain in scripts/build.js
- update docs/developers/daemon-ui for the split routing
- tests: full entry shape, text cap, block-path debugReason counterpart,
and a webui malformed_payload interleave sibling so the #7012
flush-before-guard keeps a discriminating stimulus
* fix(sdk): address round-2 sidechannel review for #8823
- build.js: bump daemon browser bundle budget 191KB -> 192KB
(195,591 bytes measured > 195,584 cap; build failed at head)
- webui: narrow the observer-mode debug guard so unrecognized_*
diagnostics reach the reducer sidechannel; only block-path debug
events are dropped
- webui: merge history-store unrecognizedDiagnostics in
applyTranscriptHistory so paged-back sessions keep diagnostics
- transcript: extract truncateTextAtLimit shared by the block and
sidechannel truncation paths
- transcript: reset unrecognizedDiagnostics on rewind alongside the
sibling per-turn state resets
- types: rename DaemonUnrecognizedDiagnostic.receivedAt to
clientReceivedAt (matches the sibling block projection)
- tests: reason-prefix conformance pin, rewind reset, narrowed guard,
history pagination merge
* fix(webui): avoid flushing sidechannel diagnostics
* fix(sdk): preserve diagnostics across rewind
* fix(webui): dedupe sidechannel history records
* fix(webui): align the paging sidechannel test with the normalizer keys
The paging test added in
|
||
|
|
5003ab3c7f
|
feat(web-shell): add transcript contract prevalidation (#9388)
* test(web-shell): add transcript contract prevalidation Freeze reproducible evidence for current transcript paths before any VS Code or HTML export production migration. - Add versioned fixtures, closed export schema, and capability gates - Probe direct-daemon and ACP identity under partial history prepend - Preserve raw adapter semantics and full write_file Turn Output diffs - Document the two-MR architecture, security constraints, and blockers * fix(web-shell): harden transcript prevalidation gates Make the evidence-only contract suite enforce the review assumptions it documents while preserving the existing runtime transcript behavior. - Run the contract suite in the required no-AK integration job - Fail closed on ambiguous identity probes and deduplicate gate kinds - Enforce manifest, hash, export safety, and renderer version boundaries - Cover visible transcript text and stable Desktop packaging semantics - Record the complete PR comment evaluation and verification outcome * fix(web-shell): close transcript prevalidation gaps * fix(web-shell): remove brittle Desktop wiring probe Keep transcript contract prevalidation at the evidence level it can actually prove. The previous source-text assertion could both reject equivalent formatting and pass unreachable packaging code. - Remove the Desktop script parser and its false behavioral claim - Mark installed-artifact verification as deferred to Desktop smoke tests - Clarify MR1 matrix, CI wiring, and provenance evidence boundaries - Refresh the hash-locked capability matrix fixture Note: This does not change Web Shell or Desktop production behavior. --------- Co-authored-by: heyang.why <heyang.why@alibaba-inc.com> |
||
|
|
1eb8a0c7f8
|
feat(review): wire --resume through /review and the review run subcommand (#9153)
Surface the local resume feature (PR #9092) on the paths a user reaches it from: - `parse-args.ts`: `/review <pr> --resume` parses to `resume: { requested, effective }`, gated on PR targets (a local review's diff comes from a live working tree with no stable interrupted state). A `--resume` on a non-PR target warns and is inert. - `run.ts`: the `qwen review run` headless wrapper takes `--resume` and passes it through to the `/review` prompt. - `SKILL.md` Step 1 gains a "Resuming an interrupted run" branch: on `resume.effective`, append `--resume` to `fetch-pr`, branch on its `resumed` JSON, run `recover-findings`, re-enter the audit loop at `latestReverseAuditRound + 1`, and read the restart bound back from `restartsSpent`. - `DESIGN.md` / `docs`: document resume as a LOCAL convenience. The CI review workflow runs FRESH — it does not pass `--resume`. A CI attempt runs no-sandbox on the reviewed PR's own code and its worktree is deleted the moment it exits, so there is no interrupted state on disk for a retry to continue; a resume would refuse `worktree-gone` and start over anyway. The retry loop and its test assert the fresh-only wiring. |
||
|
|
b6e93d27ad
|
fix(autofix): paginate review threads instead of reaching the oldest 100 (#9390)
* fix(autofix): paginate review threads instead of reaching the oldest 100 `resolve_and_reply_threads` fetched `reviewThreads(first:100)` with no pagination. GitHub returns review threads in ASCENDING creation order, so a single page is the OLDEST hundred — on a long-running PR, precisely not the threads the current round is answering. Both blocks downstream map an inline-comment id to its thread. A thread past the page is absent from `THREADS_JSON`, so an implemented Critical is never resolved and reads as still open, and a declined finding's reply is answered by silence. Those are the two outcomes the function exists to prevent. Live: 8 of the 22 open takeover PRs exceed the cap. #8403 carries 1256 threads, so one page reached 8% of them — and all 1256 are unresolved. The code already detected this: it requested `pageInfo{hasNextPage}` and emitted a `:⚠️:` when true. It just never fetched the next page. Use `gh api graphql --paginate`, which is built for exactly this shape, and slurp its node stream into the flat array both blocks already expect. On #8403 that is 13 requests in ~10s. A partial fetch is USED rather than discarded: losing twelve good pages to a rate limit on the thirteenth would resolve nothing at all, so the failure is announced and the threads in hand still map. One residual stays open and is now announced rather than implied: a thread carrying more than 100 comments still truncates, so a comment past that page is unmapped and each block falls back to the id as given. No thread in the live pool comes close. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(autofix): keep the pagination failure's reason, and pin both warnings' absence Round 1's three Suggestions, all on the partial-fetch path this PR adds. R1-1: `2> /dev/null` on the paginated fetch discarded gh's stderr — the only text saying WHY pagination stopped. The warning announced THAT it stopped, so the oncall could not separate a transient rate limit (back off) from an expired PAT (rotate) or a network failure without re-running the ~13-request query by hand. Captured to `${WORKDIR}/threads-fetch.err` with the pattern already used elsewhere in this workflow, and its tail folded into the warning. R1-2: the outer thread pagination silently depends on the inner `comments` pageInfo NOT asking for `endCursor` — gh's paginator adopts the first pageInfo carrying both fields. The `Residual:` note actively invited a maintainer to close that residual by adding it, which would hijack the thread-page cursor and stop after page one at exit 0 with no warning, silently restoring the oldest-hundred bug. Documented as load-bearing, in the comment block above the fetch rather than inside the query literal — a `#` line there is transmitted. R1-3: both new warnings were asserted only in the positive, so a mutation making either unconditional shipped green. Added the clean-run absence assertions this file's own convention calls for (321 `not.toContain` uses), and the gh stub now writes a reason to stderr on failure so the folded-in text is assertable. Verified: qwen-autofix-workflow 178 passed. Mutation-checked — restoring `2> /dev/null` and making the pagination warning unconditional each fail a test. The one remaining failure (`behaviorally replays the stale-duplicate revalidation`, 5s timeout) is identical with these changes stashed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(autofix): keep gh's error body out of the slurped review threads Round 2 of the review on #9390 found the paginated review-thread fetch poisons its own output on a partial page, and asked for two clarifications around it. R2-C (Critical) — on a failing page gh skips `--jq` and appends that page's raw response body (a rate-limit message, or a GraphQL error envelope) to stdout after the good nodes. The unfiltered `jq -s '.'` slurped it as an extra element, and both consumers below iterate `.comments.nodes[]` over every element, so the first one exited 5. This step runs under errexit, so that aborted 'Push and report' AFTER a good push had landed — the report and the markers were skipped and the job failed. That contradicts the two invariants the block documents: a resolve failure must never fail a good push, and a partial fetch is used rather than discarded. The slurp now keeps only thread-shaped documents. R2-1 — the comment block warned against adding `endCursor` to the inner `comments` pageInfo, but the outer pageInfo's field ORDER is load-bearing for the same reason: gh's cursor scanner carries its flags across pageInfo objects and breaks at the first one yielding both fields, so alphabetizing to `pageInfo{endCursor hasNextPage}` stops after page one just as silently. Said so at the query, and at the test pin that goes red on a reorder, so the pin is understood rather than bumped. R2-2 — the stderr fold dropped the `tr '\r\n' ' '` that its ten sibling sites apply. Actions parses workflow commands line by line and gh's secondary-rate-limit stderr spans two lines, so the annotation kept only the first — cutting off the words that separate a back-off from a credential rotation. Verification: `scripts/tests/qwen-autofix-workflow.test.js` 179/179; yaml parses; eslint and prettier clean. Mutation-checked all three: reverting the slurp filter fails the resolve arm with exit 5 (expected 5 to be 0), reordering the outer pageInfo fails the field-order pin, and dropping the `tr` fails the folded-reason arm on the second stderr line. * docs(autofix): correct the field-order comment's mechanism (#9390 R3-1) The comment explaining why `pageInfo{hasNextPage endCursor}` order is load-bearing described the silent stop as happening with the carried `hasNextPage` "already true" from the last inner page. That cannot produce the symptom: gh's `findEndCursor` returns a cursor only `if hasNextPage`, so a carried true would keep the walk going. The real mechanism is the opposite one. The scanner carries its flags across `pageInfo` objects and breaks at the first point both have been seen; under `pageInfo{endCursor hasNextPage}` that break lands on the outer `endCursor` while `hasNextPage` still holds the last INNER page's value — almost always false, since thread comment pages rarely truncate — and the outer page's own `hasNextPage` is never read. gh returns no cursor and the walk stops after page one, exit 0 and silent. Reworded in both places the clause was copied to: the workflow comment and the field-order pin's comment in the test. No assertion, no shell, and no query text changes; `pageInfo{hasNextPage endCursor}` and the test that pins it are untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(autofix): keep gh's thread-fetch stderr off a predictable WORKDIR path R4-1 (Critical, #9390): the review-thread pagination wrote gh's stderr to `${WORKDIR}/threads-fetch.err` and read it back with `tail -c 300`, both without a file-type guard. WORKDIR (`/tmp/autofix-review-<pr>`) is bind-mounted read-write into the agent docker sandbox, and the round that just finished ran branch code inside that sandbox, so the name is attacker-chosen by the time this step runs. A planted FIFO makes bash block on the O_WRONLY open before gh even execs, and the only reader is the `tail` that runs strictly after gh returns — so the step hangs to the job timeout with the push already landed, losing the report comment and the round markers. That breaks the invariant this block states for itself: a resolve failure must never fail a good push. A planted symlink instead turns the redirect into a truncate/write against the link target and the tail into a 300-byte arbitrary-file read folded into a public `:⚠️:`. Route the stderr through a fresh `mktemp` regular file instead, matching the `gh api user` checks elsewhere in this workflow, and remove it afterwards. The diagnostic is unchanged: the warning still carries gh's own reason, which is the only text separating a transient rate limit from an expired PAT. Test: plant a symlink at the old path, run the block through a failing fetch, and assert the target's bytes are neither overwritten nor folded into the annotation; plus assert the named path is not created at all. Mutation- verified — restoring the `${WORKDIR}` redirect turns the canary assertion red (`expected 'threads-fetch stub failure' to be 'CANARY-MUST-SURVIVE'`). The FIFO half cannot be written as a plain assertion because the pre-fix code hangs rather than fails; the same "named path is never opened" property defuses it. --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
bc2d205d29
|
refactor(ci): simplify the review checkout self-heal back to wipe-and-retry (#9327)
* refactor(ci): simplify the review checkout self-heal back to wipe-and-retry #9220 fixed a real incident (a corrupt persisted workspace made seven review jobs fail checkout on the same missing SHAs), but eight review rounds grew the heal step from ~15 lines into ~60 lines of path-guard layers (realpath canonicalization, two trailing-slash strip loops, a denylist case, a RUNNER_WORKSPACE allowlist) plus ~450 lines of tests pinning their mutation resistance. Every removed layer defended against a mangled GITHUB_WORKSPACE. That variable is set by actions/runner; anything that could mangle it — a compromised runner, a step writing GITHUB_ENV — already executes arbitrary code on the machine and needs no wipe to do damage, so the guard cannot defend against the only actor able to trigger it. The realistic contract is the :? guard: fail loud on a dropped variable. Kept and still pinned by tests: the pool wipe idiom, the sudo fallback leg (exact argv), the never-fail exit contract with named survivors, the identical retry checkout, and the continue-on-error invariants. Also dropped with the guards: the GNU-only realpath flag and its host-probe test machinery. * test(ci): pin the runner-owned GITHUB_WORKSPACE premise before the workspace wipe Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(ci): seal the live override channels into the review wipe step * fix(ci): refuse a redirected workspace and pin the clean-wipe silence Addresses the two doudouOUC findings on the simplified heal: - The wipe now validates the filesystem OBJECT at $WS, not just the string: find -P does not descend a symlinked start, so a redirected workspace logged 'wiped for a clean retry' while deleting nothing, and the secret-bearing review step would then run through the redirection. Refuse loud on a symlink or non-directory — POSIX-only, no false-positive surface (a legitimate workspace is always a runner-created plain directory), and it pins the only sudo-escalated wipe in the pool to a validated target. - The clean-wipe silence branch was unpinned: the reviewer's minimal mutant (dropping the if/fi pair) shipped an empty-list survivor warning on every heal with the suite green. The clean-wipe test now asserts the success annotation and the absence of the survivor warning; both mutants verified red. * test(ci): seal the wipe step's surviving override channels * test(ci): seal the wipe's surviving override channels, pin its signals Addresses the open review findings on the simplified heal: - The seal's premise covered declarative env, $GITHUB_ENV/$GITHUB_PATH run writes, and the pre-wipe action set, but three channels passed it unchecked: a wipe-step `shell:` or workflow/job `defaults:` wrapper re-targets the environment at exec time; SHELLOPTS rides the same bash-startup family as BASH_ENV/ENV yet sat outside the dangerous name class; and ACTIONS_ALLOW_UNSECURE_COMMANDS re-enables the legacy ::set-env:: / ::add-path:: spellings the run-text scan did not match. Each channel was reproduced green against the old seal (mutant probe) and now turns it red. - The both-legs-fail test now also pins the else-branch "could not wipe" warning, and a dedicated test pins the `[ ! -d ]` refusal for a nonexistent workspace — the plain-file test alone still passes a guard mutated to `[ -f ]`. - The non-sudo wipe leg keeps its stderr: the 2>/dev/null discarded exactly the diagnostics oncall needs when the wipe fails, and the sudo leg already ran unsuppressed. * fix(ci): refuse workspace wipe through symlinked path components (#9327) --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |