mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-02 21:04:32 +00:00
509 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e3708a5450
|
ci: remove ineffective terminal-bench workflow (#8323) | ||
|
|
46a16de5c6
|
fix(autofix): state the primary agent budget and use the step's headroom (#8257)
* fix(autofix): budget the whole round, not just the agent step The primary attempt took run-agent.mjs's 50-minute default while its step capped at 80, so a third of that step was unreachable and every "ran out of time" round ended for a reason this file never named. Raising it surfaced the larger problem: the budget was never checked as a whole. Measured on one scan (run 30646547838): setup 5-7m in earlier steps, so it never competes with the agent step; Triage and address 50m03s on #8005 round 9 (its own timer) and 12m45s on #8211; the Verification gate 22m48s on #8211 — the largest consumer in the job, and unbounded; push, report and finalize 3-4s. The old arithmetic ("80 + 20 leaves ~50 for setup, two verification passes, and reporting") predates that measurement: the real worst case was 7 + 80 + 23 + 20 + 23 = 153 against a 150-minute job, and a JOB timeout cancels the always() reporters, which is the silent round the design exists to prevent. Sized for the PRs that actually exhaust the budget (47 and 35 changed files): agent 120m under a 130m step cap, each verification gate bounded at 60m — 2.6x the measured pass, and a graceful degrade because both gates already carry continue-on-error, so a bound turns a job kill into the ordinary verification-failure path that reports. Worst case 7 + 130 + 60 + 20 + 60 + 3 = 280 against a 300-minute job, itself under the 360-minute ceiling ubuntu-latest imposes regardless. Raising the budget does not make a doomed round cheaper — each exhausted budget still pushes nothing, which is why TIMEOUT_WINDOW_CAP stops a PR after three in one window. It buys rounds that were close to finishing. The test now asserts the SUM against the job cap and the count of bounded steps, because asserting the numbers individually is exactly what let 153-against-150 pass. Mutations checked: budget over its step cap, either verification bound removed, job cap back to 150, job cap over the runner ceiling, and continue-on-error dropped are each caught. * fix(autofix): bound every long step and cap the timeout override (#8257) * fix(autofix): enforce the timeout ceiling and trim review feedback (#8257) * fix(autofix): force base-10 clamp and align stale bound with job cap (#8257) * fix(autofix): close the clamp int64 escape and pin it with a bash replay (#8257) * fix(autofix): give the timeout clamp a floor, not only a ceiling The review's Finding 1: the guard clamped only the ceiling, and the uncovered side is the likelier typo. Every comment in this file, the PR body and the operator message speak in MINUTES; this one variable wants MILLISECONDS. A maintainer told to "raise the agent time budget" who sets QWEN_AUTOFIX_TIMEOUT_MS=120 armed a 120 ms timer — every round SIGTERMs instantly, writes agent-timeout, and reports "ran out of time (timeout (120ms))" until TIMEOUT_WINDOW_CAP trips and AutoFix stops on the PR, advising the human to raise the budget they just raised. No warning anywhere in that loop, which is the exact misreport the clamp exists to prevent, reached from the other direction. A 60000 ms floor rejects every minutes-shaped value, and it also closes the `0`/`000` hole the review noted alongside it — those passed the bare regex while the message asserted the value had to be positive. The message now names the units, because a units confusion is the whole failure mode. Replayed the review's own table against the extracted block, stdout and stderr separated: 7200000, 3600000 and the floor itself pass untouched; 120, 60, 0, 000 and 59999 all clamp with a warning, alongside the over-cap, malformed, octal and int64 cases the previous round closed. The test pins both boundaries from each side (59999 clamps, 60001 does not) and asserts the warning names MILLISECONDS. --------- Co-authored-by: verify <verify@local> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: Qwen Autofix <qwen-autofix@users.noreply.github.com> |
||
|
|
253f8b8daf
|
fix(ci): harden self-hosted runner workspace ownership recovery (#8115)
* fix(ci): harden self-hosted runner workspace ownership recovery Containerised jobs (qwen-triage verify/tmux) leave root- or node-owned files in the runner workspace. When the next job's actions/checkout tries to remove them it fails with EACCES, permanently poisoning the runner for all subsequent jobs. Three-layer fix: 1. qwen-triage.yml: split the ownership-restore (chmod + chown back to the runner user) out of the conditional 'Clean up runner workspace' step into its own 'if: always()' step, so it runs even when the job is cancelled or skipped — while the container still has root. 2. qwen-code-pr-review.yml / ci.yml: after the existing chown + sudo attempts, probe each known problem dir (.qwen, .git) with touch; if unwriteable, rename it aside (mv only needs write on the parent directory, which the runner user owns). ci.yml gains the full 'Restore workspace ownership' step it previously lacked. 3. ci.yml 'Clean stale .qwen before checkout' now also removes any .qwen.stale.* directories renamed aside by the step above. Refs: runs 30339720611 (actions-runner-8), 30480422410 (actions-runner-test-14) — both EACCES on .qwen/agents. * fix(ci): address review feedback on workspace ownership recovery Repoint the verify cleanup regression test at the new 'Restore workspace ownership' step so the chmod-before-chown guard is active again, and remove renamed-aside .git.stale.* / .qwen.stale.* dirs in ci.yml and qwen-code-pr-review.yml so they no longer accumulate on self-hosted runners. * test(ci): guard unconditional ownership restore for verify and tmux (#8115) * test(ci): guard rename-aside ownership recovery in ci.yml and pr-review (#8115) * fix(ci): address review feedback on workspace ownership recovery - Make .stale.* cleanup failure visible with :⚠️: instead of silent || true; add sudo -n rm -rf fallback to ci.yml cleanup step - Add .stale.* sweep to containerised verify/tmux-testing ownership- restore steps (run as root, the only actor that can delete them) - Extend ownership recovery to web_shell_e2e_smoke and integration_cli jobs which share the same ecs-qwen self-hosted runner pool - Probe-first optimization: skip expensive recursive chown/chmod on healthy runs; only pay for the full-tree walk when a probe fails - Use mkdir/rmdir instead of touch/rm for writability probe (mkdir never follows symlinks, avoiding a planted-symlink vector) - Use GITHUB_RUN_ID.GITHUB_RUN_ATTEMPT instead of $$ for unique suffix (PID recycles on long-lived runners) - Add set -uo pipefail and $GITHUB_WORKSPACE/ absolute paths to ci.yml cleanup step - Keep rename-aside blocks byte-identical across ci.yml and qwen-code-pr-review.yml with a NOTE comment explaining why extraction into .github/scripts/ is impossible - Add :⚠️: on chmod failure in verify ownership-restore step - Add tests: new job coverage, byte-identical block assertion, .stale.* sweep assertions, cleanup hardening assertions (#8115) * test(ci): guard all four rename-aside copies in byte-identical assertion (#8115) * fix(ci): drop inert rename-aside, restore unconditional recovery (#8115) Review verification showed the pre-checkout rename-aside fallback never unblocks actions/checkout: checkout deletes every workspace entry (or runs git clean -ffdx), walking straight into the renamed dir, and the rename only fires when the runner does not own the dir — exactly when rm -rf cannot empty it either. Drop it from all four checkout jobs and the qwen-triage root sweeps, restoring the simpler unconditional chown/chmod recovery. The probe-first gating is removed for the same reason: poisoning is workspace-wide (root-owned node_modules/dist with no .qwen/.git), so a probe that only checks .qwen/.git reports "healthy" and skips the chown that main did unconditionally — a regression on runners with passwordless sudo. The layer-1 fix (qwen-triage ownership restore running as root under if: always()) is the actual root-cause fix and is kept unchanged. * fix(ci): address review feedback on ownership tests (#8115) Hoist assertUnconditional helper to module scope and reuse it in the pr-review describe block, add sudo-fallback and stat-based UID discovery assertions to guard the recovery branches that are load-bearing on non-root and containerised runners. * fix(ci): cover triage job, add chown warnings, tmux chmod (#8115) * fix(ci): assert restore-before-checkout ordering, surface sudo chown stderr (#8115) * fix(ci): widen verify restore chmod, guard symlinked .qwen, surface sudo chmod stderr (#8115) --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: qwen-code-autofix[bot] <qwen-code-autofix[bot]@users.noreply.github.com> Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com> |
||
|
|
ce8eb830d7
|
fix(triage): say what the re-run summary measured, in both languages (#8273)
* fix(triage): say what the re-run summary measured, in both languages The re-run summary told maintainers the bot had "no review of its own" whenever it had not VOTED. On #7948 and #8141 the bot had reviewed and deliberately deferred, leaving a COMMENTED review visible on the page — so the comment said the opposite of what the reader could see. It was also the one composed body in this workflow that shipped English only, sitting directly under the bot's own bilingual stage-3 review. Classify the head-commit state three ways instead of two and name what is actually there. A COMMENTED-only review now reads as "carries no vote" and lists both things that produce it: the skill deferring on purpose at 3/5 (a fork `refactor` on the approval guardrail, or a core change escalated for maintainer awareness) or an approval a push dismissed. The stage-3 comment says which; the reviews API cannot. That indistinguishability is why the warning still fires on both. My first pass silenced the defer case as routine, which would have muted the guard for the incident the existing test encodes — a dismissed approval leaving only a COMMENTED behind. Either way the PR is one approval short with the bot not supplying it, so the operator signal stays and only the wording differs. DISMISSED and PENDING are explicitly not deferrals: a push voids the bot's approval, and that is precisely when a fresh one is required. Verified by running the extracted step under its own `set -euo pipefail` with a stubbed gh: the real review payloads from #7948 and #8141 both classify as deferred; ten synthetic arms separate own / deferred / none, including human-approval-only and a vote on an older commit, which must stay `none`. The composed body was rendered through GitHub's own `POST /markdown` — 8 code spans, 6 bold runs, 2 links, no live mentions, Chinese intact. * fix(triage): address review feedback on re-run summary tests (#8273) --------- Co-authored-by: verify <verify@local> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> |
||
|
|
0b36e597d4
|
fix(triage): render the verify report as sanitized markdown, not an escaped pre dump (#8147)
* fix(triage): render the verify report as sanitized markdown, not an escaped pre dump The sandboxed-verification comment embedded report.md inside <details><pre><code> with full HTML escaping. Safe, but unreadable: the report is a curated bilingual document — tables, headings, nested <details> folds — and it displayed as a wall of raw markdown source (#8140's verify comment was the exhibit: literal asterisks, table pipes, and <details> tags shown as text). report.md now renders as markdown through emit_report, which holds the same security floor with four line-independent guarantees: every & < > is escaped and only the structural tags the report legitimately uses (details/summary/pre/code/br) are un-escaped back to live tags, so no other tag can form; the comment-open token is broken (the autofix-proven neutralizer), so no forged qwen-triage:* marker can appear in the raw body the upsert logic greps; @ becomes @, which renders identically but can never fire a mention; and unbalanced <details> opens are counted and closed, so a malformed report cannot swallow the footer. An oversized report falls back to the escaped-pre embedding wholesale (truncated markdown dangles fences and folds), as does any sanitizer failure. The tmux lane's raw-log embedding is untouched — escaped pre remains right for logs. The zero-match grep in the fold balancer carries || true: under the step's pipefail, a report with no folds would otherwise kill the whole composer. Tests: a behavioral replay drives the real emit_report — structure survives (tables, folds, no pre/code), the security floor holds (no live marker/mention/tag, entities escaped, folds balanced), and the oversize fallback produces the escaped shape; the full-render ordering pin follows the new heading. * fix(triage): cap sanitized report size and use portable ERE sed (#8147) * fix(triage): budget fold-closer overhead against the report size cap (#8147) * fix(triage): annotate emit_report fallbacks with distinct warnings (#8147) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(triage): sanitize the verify report code-region-aware and re-collapse it (#8147) Replace the unconditional sed escaping in emit_report with a node sanitizer that tells code regions apart from prose. CommonMark does not decode entities in code spans/fences, so escaping & < > @ there showed && / <T> / @pkg in the commands, types, and paths a report is read to copy. Prose is still escaped (< only; & and > are not security controls and mangling them killed && and blockquotes), code is left inert, the <!-- break stays global so no forged marker survives in the raw body the upsert greps, and folds are balanced over prose only so a fenced </details> can no longer defeat guarantee 4 (surplus closers dropped, unclosed opens closed). Wrap the rendered report in a collapsed <details> so it costs one line again instead of expanding up to 45 KB inline, narrow the tag allowlist to details/summary, bound the whole wrapped section against the size cap, and make every fallback label say "truncated". * fix(triage): close dangling code fences at EOF and test the sanitize-failure fallback (#8147) * fix(triage): defuse mentions with ZWSP and track HTML blocks in sanitizer (#8147) * fix(triage): prose-escape code spans inside HTML blocks and widen inHtml entry (#8147) * fix(triage): degrade to escaped fallback when a code fence is open at EOF (#8147) * fix(triage): fail closed on paragraph code-span and container-fence divergence (#8147) * fix(triage): fail closed on escaped-backtick and entity-forgery sanitizer holes (#8147) --------- Co-authored-by: verify <verify@local> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
a3b5b4d856
|
fix: make the test suite portable on Windows (#8050)
* ci: add temporary Windows compatibility validation
* fix(acp): resolve sandbox mounts with POSIX semantics
* test: normalize Windows runner environment
* ci: fix Windows validation environment setup
* fix: make path handling portable on Windows
* test: make suites portable across Windows
* ci: normalize Windows validation environment
* fix: finish Windows test portability
* test: finish Windows runner validation
* test: stub Windows efficacy probes deterministically
* fix(review): launch npx correctly on Windows
* fix(test): harden review probe portability
* test(cli): keep bridge spawn assertions strict
* fix: address remaining Windows-portability review feedback
- Split cursor integer validation: relax dev/ino to Number.isInteger for
Windows 64-bit file indices (which exceed 2^53) while keeping
snapshotSize/position safe-integer, since those are arithmetic operands.
- Derive session-mention unescaping from the shared SHELL_SPECIAL_CHARS set
via a new unescapeShellSpecials helper, so the escape set no longer diverges
between POSIX and Windows.
- Restore test teeth: a distinguishable realpath mapping in the weixin send
test, and exact-basename assertions in the review paths test.
- Nits: report POSIX-only skips via it.skipIf instead of if-wraps, inline
path.posix in installationInfo, name the win32 traversal assumption in
permission-manager test titles, explain the NUL-byte transcript trick, and
use a single LC_ALL=C.UTF-8 in the Windows runner workflow.
* fix: address Windows-portability review feedback
- Run the Windows runner env-config step under pwsh so Out-File writes
UTF-8 (no BOM) into GITHUB_ENV; Windows PowerShell 5.1 wrote UTF-16LE,
which the runner parsed as UTF-8 and silently left TEMP/TMP unset. Also
echo TEMP in the verify step so a regression is visible in the logs.
- Restore the not.toHaveProperty('sessionScope') assertion in the serve
bridge test: toEqual ignores undefined-valued keys, so on its own it no
longer proved the sessionScope key is truly absent.
- Drop the now-dead chmodSync on the fake vitest runner, which is invoked
through process.execPath so the executable bit is irrelevant.
* fix: address follow-up Windows-portability review feedback
- Resolve the probe's vitest entry via createRequire + the package bin field instead of a hard-coded path, with self-explaining errors
- Normalise result-path separators only on win32 so a POSIX backslash filename cannot false-match a probe
- Add a literal V1 projection test, a vitest-entry canary, and a TEMP-redirection assert; clarify intent comments
* test(cli): fix session-scoped symlink test setup
* ci: use Windows PowerShell for runner setup
* test(cli): respect localized extension list output
* ci: limit Windows validation to workspace tests
* docs(ci): clarify Windows validation scope
* test(cli): create session-scoped parent in symlink fixture (#8050)
* test(cli): cover vitest bin resolution errors
* fix(cli): preserve runtime session cleanup after merge
* fix(test): cover Windows portability edge cases
* test(core): cover session profiler without nofollow
* fix(review): expose dependencies to efficacy probes
* fix(review): isolate efficacy probe caches
* fix(review): preserve probe dependency bins
* fix(review): ignore dependency metadata files
* fix(review): address probe symlink assertion and doc comment placement (#8050)
* test(cli): tighten dependency exposure assertions
* fix(cli): use file URL for ESM import specifier in probe test (#8050)
On Windows, join() produces backslash paths that break ESM import
specifiers (\\x is parsed as a hex escape). Convert to a file URL
via pathToFileURL so the generated vitest.mjs is valid on all
platforms.
* fix(cli): skip broken probe dependencies
* test(cli): cover Windows review edge cases
* fix(windows): platform-aware archive path, symlink error handling, probe normalization
* fix(test): align archive-path assertions with mocked join and retry Windows cleanup
sessionService.test.ts mocks path.join to always join with '/', so the
readdir dir checks must match '/'-separated paths; using path.sep broke
them on Windows where sep stays '\' under the automock.
The channel webhook tests remove temp trees that Windows can still hold
briefly (extension-store watchers), turning rmdir into ENOTEMPTY; give
fsp.rm the standard maxRetries/retryDelay backoff.
* docs(review): correct probe spawn failure comment
* fix(review): address probe-runner and profiler review feedback
Distinguish a present-but-hidden vitest from a missing one in findVitestBin,
guard the remaining per-entry calls in exposeDependencies and disclose a
partial dependency farm instead of failing silently, fold case when matching
probe paths on Windows only, key the profiler's O_NOFOLLOW trade on the
platform rather than the flag's presence (with a test pinning the symlink
pre-check as the Windows guard), and normalise the platform-skip test idioms.
---------
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
Co-authored-by: Qwen Autofix <autofix@qwen-code.bot>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com>
|
||
|
|
eabf312a06
|
feat(autofix): unify local and CI flows in one skill (#8121)
* feat(core): add current PR autofix controls * fix(core): align autofix ci status wording * feat(core): add current PR Autofix watcher * feat(cli): route Autofix watcher ticks * fix(autofix): fail closed on invalid watchers * fix(acp): reject malformed autofix ticks * fix(autofix): stop malformed watcher jobs * fix(autofix): preserve unrelated cron jobs * fix(autofix): separate watcher command from workflow skill * test(autofix): cover headless watcher delivery * test(autofix): cover watcher safety edges * test(autofix): cover watcher failure paths * fix(autofix): preserve non-watcher cron jobs * fix(cli): fail closed on malformed autofix ticks * test(cli): type autofix child process mock * test(autofix): align malformed watcher coverage * test(autofix): assert detached head skips gh * fix(autofix): close watcher control gaps * fix(autofix): preserve ordinary cron queue semantics * fix(autofix): extract shared constants, validate job id, broaden off filter * test(autofix): cover malformed watcher cleanup * refactor(autofix): keep current PR controls minimal * feat(autofix): reuse project skill for local runs * fix(autofix): harden local review boundaries * fix(autofix): enforce nested review containment * fix(autofix): require local repository trust |
||
|
|
e447cbe0dd
|
feat(triage): start the verify lane alongside /triage on a pull request (#8249)
A `@qwen-code /triage` comment on a PR now starts the sandboxed verify lane too, in parallel. The two answer different questions — triage reads, verify builds and runs — and neither waits on the other; they were already sibling jobs on `needs: [authorize]`, so this is a trigger change, not a new pipeline. The trigger cannot be a second copy of the command patterns. authorize publishes `verify_lane`, and the verify job's `if` and concurrency group both read it, because re-matching the strings in the job predicate is exactly what would let the trigger and the trust classification drift: the classifier keyed on `/verify` while the predicate answered to `/triage` as well, and a `/triage`-started run would then have executed an external author's code with `verify_trust` empty — skipping the head-OID pin, the risk screen and both workspace wipes, the four controls that exist for untrusted code on a reused pool. Routing the new trigger through the same classifier is what keeps them on. The lane fails closed differently from an explicit `/verify`. An unreadable author permission or a failed head-OID snapshot still denies `/verify` outright, since the commenter asked for exactly that; on `/triage` it closes the lane only, so a flaky permission API cannot cost a reviewer their triage. Automatic per-PR triage (`pull_request_target`) deliberately does not pull the lane: the constraint is the agent budget, not runner capacity — a verify run is up to 110 minutes of model time, and every `synchronize` would spend one. Commenting is what says the PR is worth it. On a plain issue the lane stays off and no author lookup is spent. The verify lane's trigger turned out to be unpinned: deleting the gate from the job predicate left the suite green. The new test pins both predicates, and it took two tries — asserting over the whole job text could not tell `if:` from `concurrency:`, so the first version survived its own mutation, and so did the positive control written to vouch for it. Both are now sliced per predicate. Mutation results: dropping the gate from `if`, from `concurrency`, from authorize's `/triage` branch, and making the piggybacked lane deny triage are all caught; the unmutated control and the ECS kill-switch control behave as expected. Verified by executing the real authorize step with a per-user permission stub across twelve branches: /verify and /triage on a PR classify identically (trusted for a write author, external plus a pinned OID otherwise), /verify still denies on a lookup failure where /triage keeps should_run with the lane off, /tmux and plain comments are unchanged, and a commenter without write is denied throughout. actionlint, yamllint, eslint clean; the prettier warning on this file is pre-existing (fails identically with the change stashed). Co-authored-by: verify <verify@local> |
||
|
|
2ad15a9fbc
|
fix(autofix): Extend suggestion handling to ten rounds (#8247)
* fix(autofix): extend takeover suggestion window Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(autofix): use ten-round suggestion threshold Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
8efdf749ad
|
fix(autofix): guard review thread resolution (#8231)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
0d3c8641f1
|
ci: cache npm downloads for verify and tmux build steps (#7885)
* ci: cache npm downloads for verify and tmux build steps The "Install and build PR app" step in both the verify and tmux jobs runs `npm ci` from scratch every time, taking ~5m40s out of a 15-minute verify run. Add an `actions/cache@v4` step before each build that restores the npm download cache keyed by `package-lock.json` hash. Security model: the cache restore runs as root (with full Actions credentials) in a separate step. The build step itself still strips ACTIONS_RUNTIME_TOKEN/URL/CACHE_URL before running PR lifecycle scripts as the `node` user, so untrusted code cannot read or write the Actions cache. The restored cache directory is chowned to `node:node` and passed via `npm ci --cache` so the build user reads packages from the local cache without touching the cache API. Expected improvement: npm ci drops from ~4min to ~1min on cache hit, cutting total verify time from ~15min to ~10min. * ci: pin actions/cache to SHA for supply-chain security (#7885) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * ci: add npm cache comment to verify job matching tmux job (#7885) * fix(ci): use actions/cache/restore to prevent cache writes from PR code (#7885) * test(ci): pin actions/cache/restore as restore-only invariant in both lanes (#7885) * test(ci): assert npm ci consumes the restored cache directory (#7885) * fix(ci): align prepare log with npm ci cache flag and harden cache tests (#7885) * fix(ci): harden npm cache tests and document missing save step (#7885) * fix(ci): add npm cache producer and clear stale cache before restore (#7885) * test(ci): harden npm cache guards per review (#7885) * fix(ci): make npm cache test robust to prettier YAML quoting (#7885) Prettier reformats the hashFiles() key value from single-quoted YAML (with '' escaping) to double-quoted, breaking the raw-string comparison in the cache producer test. Compare parsed scalar values instead. * fix(ci): run npm cache producer on the consumer runner so restores hit (#7885) actions/cache scopes an entry by a hash of the literal cache path plus the compression method. The producer ran on ubuntu-latest (host path, zstd) while the verify/tmux consumers run in a node:22-bookworm container (container path, gzip), so the versions never matched and every restore was a guaranteed permanent miss. Move the producer onto the same runs-on + container so path and compression match by construction, give the restore step an id and report cache-hit to the job summary so any future miss is visible, and point the stale-cache clear step at $RUNNER_TEMP so it removes the container path rather than the inert host path. --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com> Co-authored-by: Qwen Code CI <qwen-code-ci@users.noreply.github.com> |
||
|
|
f66bfaad57
|
fix(release): keep notes anchored and cap the release body (#8199)
* fix(release): keep notes anchored and cap the release body
The v0.21.2 publish failed at "Create GitHub Release and Tag" with
HTTP 422 "body is too long (maximum is 125000 characters)", after every
npm package had already been published.
Stable releases are tagged on their own release/* branch and merged back
to main only afterwards, so the previous stable tag is never an ancestor
of the branch being released. The ancestor guard therefore dropped
--notes-start-tag on every stable release, and without an anchor GitHub
generates notes across the entire branch history (8000+ commits), which
overruns the body limit.
Always pass the previous tag instead: GitHub diffs it through the merge
base, which is how v0.21.1 produced a 27KB body from a tag that was
equally divergent. Generate the body through the generate-notes API
first so an oversized changelog is truncated on a UTF-8 boundary, and
degrade to an unanchored body and then a minimal one, rather than
aborting a release whose packages are already on npm.
* test(release): pin the anchored release-notes contract
The workflow test asserted the ancestor guard that dropped
--notes-start-tag on every stable release. Assert the replacement
instead: the previous tag is always passed to generate-notes, the body
is capped, and ancestry no longer decides whether notes are anchored.
* refactor(release): extract release-notes capping into a tested helper
The degradation chain lived inline in the workflow bash, so nothing
pinned that a capped body plus its footer stays under GitHub's 125000
character limit, that truncation never splits a multi-byte character, or
that the chain always yields a non-empty body. Move it to
.github/scripts/cap-release-notes.mjs with a collocated node:test suite,
matching the other workflow helpers.
Capping on code points rather than bytes drops the head/iconv dance and
makes the surrogate-pair case testable. The helper also absorbs the
empty-body fallback, which caught a real defect: gh writes the API error
payload to stdout when generate-notes fails, so a doubly failed call
would have published `{"message":"Not Found",...}` as the release body.
Discard a failed attempt's output instead.
* test(release): exercise the surrogate-pair cut and footer-overflow branch (#8199)
---------
Co-authored-by: Qwen Code Bot <qwen-code-bot@alibabacloud.com>
|
||
|
|
cc508dd7ba
|
fix(autofix): salvage race-lost pushes by merging the moved head and retrying (#8042)
* fix(autofix): salvage race-lost pushes by merging the moved head and retrying The review-address push is one-shot: when anything pushes to the PR head during the agent's ~50-minute window, the final push dies 'fetch first' and the entire verified agent run is discarded. The per-PR head-write concurrency group cannot prevent this — it serialises this repo's workflows, not the PR author or the fork side. Observed twice in one day (#7983 after a 56-minute run, #7985 after 43 minutes). On rejection, fetch the moved head, merge it into the local line, and retry (bounded at 3 attempts). Merge rather than rebase: the agent's own conflict-resolution rounds create merge commits, and a rebase would flatten them and can silently re-introduce the conflicts they resolved. The merge result descends from the remote head, so the retried push is a fast-forward and rewrites nothing. A genuine content conflict aborts the merge and falls through to the existing failure path unchanged. When a salvage merge happened, the round report discloses that the round's verification predates the merge so mid-run commits get re-checked by a human. * fix(autofix): address salvage-loop review findings - Gate the PUSH_RACE_MERGED disclosure on HEAD actually advancing: a transient push failure (upload timeout, 503) on an unmoved branch no-ops the merge ('Already up to date') and must not tell the reviewer to re-check mid-run commits that never existed. - Annotate the salvage fetch failure with ::error:: like the two adjacent failure paths, so a deleted fork branch or network error does not kill the step with an unannotated exit 128 under bash -e. - Re-pin the same-repo push URL construction in tests: it lost its old 'origin "${BRANCH}"' pin in this rework, leaving a ${REPO}→${HEAD_REPO} mutation (malformed remote in the same-repo case) unkillable. * test(autofix): restore dropped mutation-killing pins and add structural assertions (#8042) * test(autofix): pin exit 1 in the give-up guard regex to kill the deletion mutation (#8042) * test(autofix): pin exit 1 in the fetch-failure and merge-conflict salvage paths (#8042) * test(autofix): strengthen salvage-test pins to kill init-value and capture-order mutations (#8042) --------- Co-authored-by: verify <verify@local> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
f6ae9202b1
|
feat(autofix): per-source feedback budget in Critical-only mode (#8071)
* fix(autofix): never defer maintainer feedback in Critical-only mode Critical-only mode (after 5 change-producing rounds) classifies feedback lexically: only a literal **[Critical]** tag or a CHANGES_REQUESTED review survives; everything else is deferred before the agent reads a word of it. That rule was built to stop the review bot's suggestion ping-pong, but it catches maintainers too. Observed four times in two days (#8037, #7944, #7885, #7799): a maintainer's review with explicit merge-blocking findings — #8037's said 'I'd fix before merge' on a correctness bug and a security-adjacent one — was wholesale-deferred as one 'non-Critical item', and the bot then reported 'No Critical feedback. The Issue-level comments sections are empty', which was lexically true and substantively false. The bot's own advertised definition ('correctness bugs, security issues, or formally requested changes') is exactly what the deferred comments contained; the agent that could have applied that definition never saw them. The lexical test now applies exclusively to the review bot's output: - All three actionable filters (reviews, inline, issue-level) pass anything not authored by the review bot straight through in Critical-only mode — the agent judges maintainer feedback on content, as everywhere else. - All three deferred-list builders keep only review-bot items, so a maintainer comment can never appear as an 'audit record'. - The deferral note says what is actually deferred (the automated reviewer's non-Critical suggestions), states that maintainer feedback is never deferred, and names the exit (@qwen-code /retry opens a fresh counting window). - SKILL.md's Critical-only policy now marks everything rendered in the actionable sections as in scope, so the agent does not re-refuse what the filter passed through. Behavioral test updated both ways: maintainer comments/reviews stay actionable in Critical-only mode across all three sources, bot suggestions still defer, and structural pins hold the bypass and the bot-only select in all six filters. * feat(autofix): per-author feedback budget in Critical-only mode Follow-up to the author-based split, prompted by the obvious counterexample: a human account can host an automated reviewer loop with the exact regeneration property the review bot has — feedback re-generated after every push at zero marginal cost — so 'not the bot' cannot mean 'never throttled'. An account is an accountability unit, not a throttle; the brake has to key on measured regeneration. Unified model: once Critical-only engages, every source has a bounded budget of untagged feedback batches per counting window. The review bot's budget is zero (all deferred, as before). A human's is CRITICAL_ONLY_HUMAN_BATCHES (2) CONSUMED batches: feedback items are bucketed into the (prev marker ts, marker ts] span that evaluated them, only spans from Critical-only rounds count, and an author needs K distinct consumed spans before their new untagged feedback defers. Fresh unevaluated feedback never counts against its own author, and the census is window-scoped, so /retry resets the budget with the window. The observed cases (#8037/#7944/#7885/#7799 — one or two late verification reports each) stay fully served under K=2; a looped reviewer is throttled after 5+K driven rounds instead of grinding to the 100-round cap. Past the budget, continuing requires one conscious act — **[Critical]**, a Request changes review, or /retry — which is precisely what separates intent from automation. Over-budget authors are named in the deferral note with those exact escapes. Tests: the six filter replays gain over-budget cases both ways (the tagged/CR escapes survive even over budget), and the budget census itself is replayed over fixture files — two consumed critical-tail batches list the author; one batch, pre-Critical batches, unconsumed feedback, untrusted authors, and command comments never count. * fix(autofix): fix deferred-feedback bash quoting and drop a dead jq binding (#8071) * test(autofix): exercise census window-isolation guard with a stale-window fixture (#8071) * test(autofix): make census command-exclusion observable; surface census stderr (#8071) * fix(autofix): exclude never-deferrable feedback from the budget census (#8071) The Critical-only per-author budget census counted every trusted review, inline comment, and issue comment, including feedback the deferred renderer would never defer: **[Critical]**-tagged comments, Request changes / APPROVED reviews, inline replies rooted at a Critical comment, and inline comments attached to a Request changes review. A maintainer who followed the documented escape hatches (tag Critical, request changes) thereby spent their own budget and had later untagged feedback silently deferred — the exact bug this PR fixes, re-created one level down. Mirror the three deferred-builder predicates in the census item filter so a batch is counted only when it is actually deferrable. Extend the census replay test with protected authors (Critical-only, Request changes, APPROVED, Critical-rooted replies, Request-changes-review inlines, the review bot as a trusted MEMBER, and a sentinel-ts marker probe) that each carry two consumed-span batches yet must stay absent, so dropping any one exclusion now fails the suite. Also fold bash's stderr into the bash -n guard assertion so a future quoting regression reports the syntax error, not just a non-zero exit. --------- Co-authored-by: verify <verify@local> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
4dab39c8d7
|
fix(autofix): answer round-cap refusals on the PR instead of only in logs (#8067)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
* fix(autofix): answer cap-gate refusals on the PR instead of only in logs Observed on #7836: the fleet shepherd detected a merge conflict, posted 'dispatched the autofix loop to resolve it', and the dispatch died at the scan's round-cap gate with only a log line — the PR page showed a promise, the run showed green, and the conflict sat unhandled for hours. Three silences stacked: the standard-management cap itself is silent (the pause notice was takeover-only, so #7836 hit 10/10 with zero PR-visible notice), the forced-dispatch refusal is silent, and the shepherd dedups per head SHA — a capped PR gets no pushes, so its head never changes and conflict handling froze permanently. Two scan-side changes (the shepherd stays untouched — the windowed round computation lives in the scan and duplicating it would drift): - A FORCED dispatch (shepherd conflict lever or a human) refused at the cap gate now answers on the PR: cap value, what stays unhandled, and the two recovery commands (/retry for a fresh window, /takeover for the raised cap). No dedup — the shepherd sends at most one dispatch per head, and a human asking twice deserves two answers. - The cap pause notice covers ALL managed PRs: the takeover variant keeps its wording, standard bot PRs get their own (/retry or /takeover). Same marker, same once-per-window dedup, same consent and PAT-identity checks — skip wins everywhere, and only the takeover variant requires the label to still be present. After a re-arm the next scheduled scan picks the PR up normally (conflict targets are label-independent), so the frozen-head loop resolves without any shepherd change. * test(autofix): replay the cap-notice consent gate across label/takeover permutations (#8067) * fix(autofix): gate the loud cap-refusal on workflow_dispatch (#8067) FORCED_PR is populated for every trusted pull_request_review (route emits pr_number for those), not just workflow_dispatch, so on a capped PR each review submission landed in the un-deduped refusal branch — 7 "Dispatch refused" comments on #7836 where 2 carried the information. Answer only workflow_dispatch (the shepherd lever or a human); review submissions stay covered by the once-per-window pause notice. Adds a verbatim behavioral replay of the guard so a dropped EVENT_NAME condition fails the test. --------- Co-authored-by: verify <verify@local> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
f4cd6e1d8b
|
fix(ci): gate the attachment guard before it allocates a runner (#8095)
Measured on a congested pool: 88 active jobs, 72 hosted and 15
self-hosted. The self-hosted 15 were all running with zero queued; the
hosted 72 were contending, and 20 of them were the SAME job —
`remove-suspicious-attachments`, all queued, none running.
Its real cost is not the work. Recent completed runs:
queue=629s run=5s queue=568s run=2s
queue=518s run=2s queue=340s run=3s
Two to five seconds of API calls behind up to ten minutes of queueing.
And almost none of it needed to happen. The trust check lived INSIDE the
github-script, so a runner was queued, allocated and started before the
job could decide it had nothing to do. Over the 200 most recent comments
on this repo: 184 from trusted associations, 9 from bots, 7 actually
needing a scan. 96.5% of these runs existed to print "Trusted author;
skipping".
Two changes:
- Hoist the association and bot checks into the job `if:`. GitHub
evaluates `if:` BEFORE allocating a runner, so a trusted comment now
costs nothing. The script keeps its own copies: the gate is an
optimisation, not the control, and the two must be able to disagree
without becoming unsafe. Every ambiguity therefore resolves toward
RUNNING the scan — an unrecognised payload yields an empty
association, which is not in the trusted list, so the job runs.
- Add a per-comment concurrency group with cancel-in-progress. The
workflow listens on `edited` as well as `created`, and the bot PATCHes
its own comments constantly, so repeated edits of one comment stacked.
The scan reads the comment's CURRENT body, so a queued earlier scan is
already stale and cancelling it loses nothing. (Contrast the verify
lane, where cancel-in-progress is deliberately false because a
cancelled run destroys evidence.) The key falls back to run_id so an
unexpected payload gets its own group instead of serialising every
scan into one.
Deliberately NOT moved to the self-hosted pool, though it would fit
technically (no checkout, no PR code, API calls only): the 20 stacked
jobs were duplicates, so relocating them just fills the ECS pool
instead — and that pool is what /verify and /triage depend on. It also
holds issues:write while processing untrusted comment bodies, which
belongs on ephemeral hardware rather than reused machines.
The `if:` semantics are verified against all payload shapes — 12 cases
covering both `comment.*` and `review.*` associations, bots, and
missing/empty payloads, each asserting which direction it resolves.
CONTRIBUTOR is deliberately NOT trusted: a merged PR does not make
someone's links safe.
Mutation-verified 6/6: dropping the review payload path, dropping the
bot check, adding CONTRIBUTOR to the trusted list, turning off
cancel-in-progress, collapsing the group to a global key, and inverting
the gate so untrusted comments are the ones skipped — each turns a test
red. The last is the one that matters; it is the only mutation here that
would be a security regression rather than a cost regression.
148/148 tests across both suites; actionlint exit 0; prettier and eslint
clean.
Co-authored-by: wenshao <wenshao@example.com>
|
||
|
|
6b0d2bbca0
|
feat(triage): make /verify evidence screenshots actually possible (#8016)
* feat(triage): make /verify evidence screenshots actually possible
Measured across the 14 real verify reports on this repo: tables appear
in 14/14 (4 to 31 rows), images in 0/14. The hosting machinery has been
complete the whole time — pr-assets branch, PNG magic check, 8 images,
2 MB cap, sanitised names — and it has never once been fed.
Two independent causes, both fixed:
1. The agent physically could not produce a PNG. It runs as `node`
under `env -i` with HOME pointed at a per-run scratch dir, so
`playwright install --with-deps chromium` needs an apt it has no
rights to and would re-download ~170 MB into a directory deleted at
the end of every run. Chromium is now installed by ROOT in the tools
step into a shared, world-readable PLAYWRIGHT_BROWSERS_PATH, and the
agent env carries that path plus QWEN_VERIFY_CHROMIUM=1 — but ONLY
when the install actually succeeded, so the variable's absence is a
real signal rather than a stale promise. The install is best-effort:
a failure warns and the run continues to a text-only report, which
is what every run has produced anyway.
2. The skill discouraged it. Captures were "Optionally … use them when
text cannot carry the oracle", with TUI rendering as the only
example — so an agent verifying a daemon or a CLI correctly
concluded text carried the oracle and skipped. It now says produce
them whenever a harness ran, and says WHY: a table is the agent's
claim about what happened; a capture of the run is a witness that
the numbers came from a real execution, which is the part a reviewer
cannot get any other way. It names the three highest-value shots
(A/B cells side by side, the mutation matrix as printed, raw harness
output behind a headline number) and forbids `playwright install`
explicitly, so the failure mode of cause 1 cannot be rediscovered by
an agent burning budget on it.
Affordable now because #8014 raised the agent budget to 120m; at 20m a
browser download alone was most of the run.
Mutation-verified 4/4: handing the agent the variable unconditionally,
failing the job on a chromium install failure, reverting the skill to
"Optionally", and dropping the do-not-install warning each turn the
test red.
90/90 tests; prettier, eslint, actionlint and shellcheck clean.
* fix(triage): match browser to lockfile and fix pr-assets branch conflict (#8016)
Split the chromium install into system deps (tools step, as root) and
browser binary (new post-checkout step, as node using the checkout's
own Playwright). This eliminates the version pin that produced an
unusable browser tree.
Change evidence hosting from a bare pr-assets leaf branch — which
cannot coexist with the existing pr-assets/* namespace — to per-PR
branches (pr-assets/pr<N>-verify) with orphan-init fallback.
Update terminal-capture skill to warn CI agents away from the
forbidden playwright install command, and close test blind spots
for conditionality (M1b), version matching (M5), and marker
ordering (M6).
* fix(triage): unpin Playwright install-deps so apt list tracks lockfile browser (#8016)
* fix(triage): assert the browser path reaches the agent, not just the flag
Review finding on #8016, verified by mutation before accepting it:
deleting `"PLAYWRIGHT_BROWSERS_PATH=$CHROMIUM_PATH"` from the agent env
left the test green. That is the nastiest arm of this feature — the
agent is TOLD chromium is available via QWEN_VERIFY_CHROMIUM=1, then
Playwright looks in the default ~/.cache/ms-playwright instead of the
shared install and every capture fails, so a successful install still
degrades to a text-only report.
The test now asserts both variables and that BOTH sit behind the
success guard. Mutation-verified 2/2: deleting the path line fails on
the containment assertion, and hoisting it outside the guard fails on
the ordering one (`expected 21928 to be less than 21834`).
Not changed, because the other two findings do not describe this
codebase — replies on the threads carry the evidence.
90/90 tests; prettier and eslint clean.
* fix(triage): cover orphan-init delivery and delete verify asset branches
Two review findings on #8016, both about the per-PR branch scheme the
autofix bot introduced on this branch (
|
||
|
|
cfcc711a70
|
feat(autofix): back off scan inspection of idle candidates (#8049)
* feat(autofix): back off scan inspection of idle candidates The scheduled scan inspects every candidate every tick. The takeover pool doubled in two days (28 open takeover PRs, 8 of them idle in 'nothing new' state for 10+ hours), and idle candidates crowd the two SHARED budgets: MAX_CANDIDATE_INSPECTIONS (60) and the 10-target cap. Observed on #8002: freshly engaged, admitted by the 09:03 scan, then deferred by the target budget while long-idle PRs re-confirmed their idleness yet again. Candidates whose list-provided updatedAt (no extra API call) is older than 24h are now inspected on roughly every 4th scan, on a deterministic slot keyed by PR number and UTC hour so no PR waits forever. The skip is free — it sits with the busy skip before the inspection-budget increment. Safe by construction: every real wake-up bumps updatedAt (reviews, comments, labels, pushes) or routes in real time anyway, so the only thing deferred is the scheduled re-confirmation of idleness plus worst-case a few hours of base-conflict-detection latency for a PR nobody touched in a day. The forced-dispatch path never builds the list files, so a forced PR is always inspected. * feat(autofix): idle-backoff review follow-ups - Corrected the comment's cost model: idle candidates hit 'continue' before the TARGETS append, so they never contend for the 10-target cap — the real win is the shared inspection budget plus the serial scan-walk latency (the walk is what delayed #8002's pickup by ~6 minutes), and the comment now says exactly that. - Slot quantum changed from the hour to the scan tick (600s, the same quantum as ROT_OFF): an hourly slot against the */10 cron meant 6 back-to-back inspections then a ~3h blind window per PR — same 25% average, terrible shape. The gap is now bounded at ~30 minutes, which is what the operator-facing strings promise ('gap ≤30m'). - The two scan-only signals updatedAt cannot see (a base conflict appearing when main moves; still-red checks awaiting the redcheck marker) are named in the comment instead of papered over. - The per-candidate jq fork became a single precomputed set + a bash substring test, matching the busy skip's idiom and the 'free' claim. - Tests: the skip predicate and set builder got a behavioral replay (idle+out-of-slot defers, idle+in-slot inspects, fresh inspects, missing-from-lookup inspects); the two byte-distance assertions became a loop-head slice (comment growth cannot red-light CI, and budget-consuming code between the skips and the increment fails); the --json field pin is order-independent; the 3600 quantum is pinned OUT. * test(autofix): pin the null-updatedAt defensive guard in idle-backoff replay (#8049) * fix(autofix): extract idle-backoff predicate from workflow, fix gap bound 30→40m (#8049) * test(autofix): pin takeover-prs.json in idle-backoff replay (#8049) * fix(autofix): state idle-backoff gap probabilistically, unpin quantum (#8049) Round-2 verification showed the scheduled scan lands every ~40-70 min on this repo, not every 10 min, so the (epoch/600)%4 slot is an independent ~25% draw per scan, not a deterministic 1-in-4 rotation. The gap is geometric (measured median ~2h, p90 ~6h), not bounded at ~40m. Reword the operator-facing strings (skip echo, fleet row) and the workflow comment to state the behaviour probabilistically and drop the false "bounded" / "no PR is unlucky forever" claims; correct the misattributed #8002 latency (queue/startup, not the serial walk). Relax the tests so a truthful number is not a CI failure: pin the mod-4 time-quantum shape instead of the exact 600s constant, and drop the /3600 exclusion that forbade the better-tailed quantum. Mechanism logic is unchanged. --------- Co-authored-by: verify <verify@local> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> |
||
|
|
d5ac7a9642
|
fix(autofix): post the takeover engage ack from the command itself (#8043)
* fix(autofix): post the takeover engage ack from the command itself The engage ack rode a pull_request:labeled round-trip: takeover-command applies the label, the labeled event routes, and the takeover-ack job posts the confirmation. That event has now been observed to simply not fire twice in one day (#7999 — the author read the silence as failure and removed the label; #8002 — an engaged fork PR with no ack for hours), and fork label events can never ack at all since they carry no secrets: a fork /takeover stayed silent until the next scan picked the PR up (2h41m on #7993). takeover-command now posts the engage ack directly after applying the label — every admission gate has already passed at that point, so 'engaged' is truthful for in-repo and fork PRs alike; the fork variant adds the expectation that the first round comes from the next scheduled scan. The route side suppresses the label-path ack when the label sender is the bot (only the ack: the immediate scan still routes), and the review-scan's existing first-pickup ack dedups against the command's comment and heals it if the post failed. Two more silent paths become audible while here: a /takeover on a stacked (non-main-base) PR now refuses out loud instead of dropping with only a log line, and a /takeover stop on a non-main PR now proceeds to remove the label instead of leaving it stuck. * fix(autofix): ack command-driven releases directly and key the scan grace on the label actor Review follow-up: the engage-side fix left the release side on the fragile round-trip — a loud add next to a mute stop re-creates the exact 'did it work or did the event get lost?' ambiguity this PR set out to remove, now on release. And a fork or non-main release could never ack at all (fork unlabeled events carry no secrets; the route ignores non-main releases). - takeover-command now posts the release ack directly after removing the label, mirroring the engage side, with the same three variants as the ack job (plain release / bot-authored / bot-authored+skip) chosen from the same PR_INFO the gates used. The route suppresses the unlabeled-path ack when the label sender is the bot. - The scan's first-pickup grace is keyed on the label event's actor: a bot-applied label defers only 45s (the command's own write is seconds behind the label — fork or in-repo alike), so the immediate scan still heals a failed command ack instead of slipping to the next scheduled tick, and an ic.json snapshot taken between the label write and the command ack cannot double-post. A human in-repo label keeps the 3-minute ack-job grace; a human fork still posts right away. - The fork note now says 'usually within minutes', matching the */10 cron instead of contradicting it by 6x. - Tests kill the two surviving mutations from review: the EN/ZH fork-note swap (per-language-half assertions) and the warning fallback downgraded to '|| true' (both fallback strings pinned). * test(autofix): behaviorally pin the LAST_LABELED_BY jq extraction (#8043) * fix(autofix): correct stale comments about scan heal timing and command blast radius (#8043) * test(autofix): pin release-ack body identity across the command and ack jobs (#8043) --------- Co-authored-by: verify <verify@local> Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com> |
||
|
|
c631c0448e
|
fix(autofix): cumulative timeout breaker, narrowed retry prompt, truthful handoff wording (#8044)
* fix(autofix): stop repeat-timeout PRs with a cumulative breaker and narrow the retry prompt The consecutive-failure cap resets on every pushed round, so it never fires on the observed failure shape: timeouts interleaved with successes. #7929 burned three 50-minute agent timeouts (with pushed rounds in between) and #7846 two — each a full agent budget with nothing to show, invisible to the breaker by construction. Three changes: - Cumulative timeout breaker (TIMEOUT_WINDOW_CAP=3): time-budget exhaustions are counted over the whole counting window, successes in between included. At the cap the PR goes terminal with a skip-until-/retry headline, exactly like the consecutive breaker. The census reuses PRIOR_HEADS, so it is window-scoped and a re-arm clears it. - Prompt narrowing from the second attempt: when the current window already contains a timeout marker, the prepare step appends a budget warning to feedback.md — smallest blocking subset first, commit early, defer the rest explicitly — instead of re-running the identical address-everything prompt into the same wall. - Truthful gate-rejection handoff: 'A human should take over this PR' read as a full release, but the loop keeps managing the PR (new feedback, base conflicts) — #7929 posted it and then kept pushing rounds. The headline now says exactly which half is over: this item will not be retried automatically, management continues. * fix(autofix): timeout-breaker review follow-ups All six review findings addressed: - The narrowing prompt now restates the SKILL deferral contract instead of pointing at 'your summary': deferred findings stay out of resolved-comments.txt and get a comment-replies.json entry so every open thread carries its reason — the summary-only shortcut is exactly what a budget-pressured agent would otherwise take. 'skip refactors entirely' became 'decline with a one-line reason' for the same never-drop-silently rule. - The narrowing census counts timeouts SINCE THE LAST SUCCESSFUL ROUND, not cumulatively: a push falsifies 'not converging', and one old timeout must not degrade every later round of a 100-round window. The breaker stays cumulative (a push does not make the next timeout cheaper in budget terms). - The breaker headline states what the census measured ('this counting window now contains N time-budget exhaustions') instead of 'stopped after N' — the tripping round can itself be a gate rejection, which is the exact rollout state of #7929/#7846. - Both census needles anchor on the verbatim emitted headline ('AutoFix ran out of time before finishing') — first lines can embed provider error text, so the loose phrase could count a model error as a timeout. - The precedence assertion got teeth: 'consecutive' alone matched both branches; it now asserts the consecutive breaker's own phrase and the absence of the timeout one (the if-true guard mutation now fails). - The narrowing census got a behavioral replay over fixture ic.json: push-resets, trailing counts, legacy no-win markers under 'none', old-window exclusion, author filtering — plus a pin on the -ge 1 trigger so a threshold mutation cannot leave the feature inert. * fix(autofix): state what the timeout breaker measured, pin the inherited guarantees Second-round review follow-ups: - The breaker headline no longer infers 'is not converging' — a window can carry 40 pushes and still trip the (deliberately cumulative) cap, so the sentence now states only what the census measured: N full agent runs that pushed nothing. - Two guardrail tests pin behavior that held only by construction: a non-timeout failure landing on an already-capped window still trips the breaker (the documented rollout state — an 'only count when this round timed out' cleanup would silently delete it), and the transient-API-error exemption inherited from the outer guard (a refactor hoisting the block out of it must not mass-terminate every in-flight PR during a provider outage). * fix(autofix): name a gate rejection only when the gate ran; pin the timeout breaker's stale-base exemption Third-round review follow-ups: - The handoff headline no longer claims 'the verification gate rejected the attempt' for every outcome=failed verdict: that branch is reached for five distinct failures (failure.md abort, dirty tree, unchanged branch, missing summary, and a real gate rejection), only one of which is a gate decision. The clause is now gated on gate-rejection.md, which reject_fix is the sole writer of — so the other four paths keep the neutral framing instead of repeating the very wording-doesn't-match-behaviour bug this PR fixes. A replay test pins both halves: the clause appears when gate-rejection.md exists and is absent when it does not. - The timeout breaker's stale-base exemption is now pinned by a replay (five in-window timeouts plus a stale-base retry current round stays retryable), matching the existing transient-API-error pin: a refactor hoisting the timeout block out of the outer guard would otherwise delete this exemption silently. - The prepare-step narrowing census jq now fails open (2> /dev/null || true) like its report-step sibling, so a malformed ic.json drops the budget warning instead of aborting prepare under errexit and turning the round into a terminal 'could not start evaluation' handoff. - The decay comment now matches the code: the warning resets on a push/no-op round but fires on every failing round until then (gate rejections included), which is correct since nothing has converged. * fix(autofix): clear stale gate-rejection.md in repair cleanup and pin headline template (#8044) * test(autofix): cover no-op reset branch in timeout census (#8044) --------- Co-authored-by: verify <verify@local> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
25f5147677
|
feat(autofix): post a takeover milestone digest every tenth pushed round (#8046)
* feat(autofix): post a takeover milestone digest every tenth pushed round The takeover round cap (100) bounds runaway but carries no signal about when a human should step in: #7469 ground to round 12 over seven days of takeover with fifteen pushes, and the only place that trajectory was visible was the Actions logs. Every 10th pushed round under takeover, the report step now posts a window-scoped census on the PR itself — pushed fixes, no-change reviews, timeouts, rejected attempts, base updates — plus the three options (keep going / split or reduce / release takeover). The digest is a separate comment with its own autofix-milestone marker and no autofix-eval marker, so every census (round, consecutive-failure, watermark) ignores it and the feedback filters keep it out of the agent's prompt. Posting is best-effort: a digest failure never fails a good push. The rejected-attempt count matches both the current and the reworded gate-rejection headline so the census cannot silently zero; base updates carry no win= field and are windowed by timestamp instead (the window key is the engage ack's created_at). * feat(autofix): milestone digest review follow-ups - Residual bucket: every outcome the four buckets missed (crash, model error, gate error, infra) now lands in 'other round(s)' — a window that burned 80% of its budget on crashes was rendering as four zeros, QUIETER than a healthy window, the inverse of the digest's premise. - Crossing trigger: fire on the first pushed round once 10+ rounds have accumulated since the last digest in this window, instead of an exact %10 hit — failure rounds advance the counter too, so push@9/crash@10/ push@11 skipped the digest forever on exactly the failure-heavy PRs it exists for. - The success log is chained to the post; a failed comment no longer logs 'posted' after its own warning. - WINDOW=none says 'since the PR opened (no counting window yet)' instead of claiming a current window while counting all time. - A census that parses zero window markers at round 10+ skips the digest instead of posting a fabricated all-zero one. - autofix-milestone joins BOT_COMMENT_FILTER (marker inventory). - The timeout needle anchors on the verbatim emitted headline. - The pin-only test became a behavioral replay: the digest block runs under bash with a stubbed gh over fixture ic.json histories — bucket counts, residual loudness, crossing suppression/resumption, old-window isolation, none-window phrasing, non-pushed/non-takeover gating, and the empty-census skip. The OUTCOME == fixed conjunct is pinned. * test(autofix): cross-pin digest census needles to headline emission sites (#8046) * test(autofix): widen rejected-headline cross-pin to match #8044 reword (#8046) * test(autofix): behaviorally cover the digest comment-failure branch (#8046) --------- Co-authored-by: verify <verify@local> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
36fe53d421
|
feat(triage): raise the /verify agent budget from 25m to 120m (#8014)
* feat(triage): raise the /verify agent budget from 25m to 120m
Maintainer decision: give /verify enough time to do what a maintainer's
local round does, and add machines if the pool gets tight.
Three values encode this one budget and each has its own silent failure
when edited apart, so all three move together:
- agent kill 25m -> 120m (the graceful budget; ships a partial
report on expiry)
- watchdog secs 1500 -> 7200 (distinguishes that kill from an OOM;
left low, a late 137 is mislabelled
`timeout` and publishes "partial
evidence" for a crash)
- job timeout 60m -> 150m (guards infra hangs only; it must clear
agent + install/build + overhead or it
kills the container mid-run and the
ship-what-ran path never executes)
150 rather than 130: measured install+build is ~6m (run 30284341325,
npm ci 3m00 + build 2m40), budgeted at 15m for a cold cache, plus ~5m of
tools/checkout/pin/upload/cleanup — worst case ~140m, leaving 10m of
headroom.
The skill's advertised budget moves too, and this is the part that
actually changes behaviour: it read "≈ 20 minutes (hard 25-minute
kill)", and an agent obeying that would have self-limited no matter what
the workflow allowed. It now reads 110/120 and says what the extra time
is FOR — bisecting a threshold through the real code path, compiling an
intermediate build to separate the halves of a bundled fix, adjudicating
mutation survivors, driving a real daemon end to end. It also says
plainly that spending it on breadth is the one way to waste it; the
rule that one proven load-bearing claim beats ten unverified
observations does not relax because the clock did.
Pinned by a new test that asserts the RELATIONSHIPS rather than the
numbers: watchdog == agent budget in seconds, job >= agent + 20m, and
the skill's advertised hard kill == the workflow's, with the soft budget
strictly below it. Mutation-verified 4/4 — leaving the watchdog at 1500,
the job at 60, the skill at 25m, or raising the soft budget above the
hard kill each turn it red.
Cost, stated so it is a decision and not a surprise: a verify run now
holds one ECS slot for up to 2.5h instead of 1h. Concurrency is
unchanged (per-PR group, cancel-in-progress false), so this reduces how
many distinct PRs can verify at once, not how many runs a PR can queue.
90/90 tests; prettier, eslint and actionlint clean.
* fix(ci): pin soft-budget lower bound in verify consistency test (#8014)
* fix(ci): harden verify budget guard and clarify budget is a ceiling (#8014)
* fix(ci): measure elapsed before artifact copy and tighten budget guard (#8014)
* test(ci): pin elapsed-time chain in verify budget drift guard (#8014)
* refactor(ci): derive verify watchdog threshold from the agent budget (#8014)
The watchdog threshold that tells a graceful timeout (137 at the budget)
from an OOM kill (137 before it) was a bare `7200` coupled to the `120m`
agent timeout only by a comment. Define the budget once as AGENT_BUDGET_M
and derive both the timeout (`${AGENT_BUDGET_M}m`) and the threshold
(`$((AGENT_BUDGET_M * 60))`) from it, so editing one cannot silently
desync the other. The consistency test now asserts that derivation rather
than re-extracting two independent literals.
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
---------
Co-authored-by: wenshao <wenshao@example.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
|
||
|
|
6097d7ab63
|
ci: auto-minimize comments from org-blocked users (#7899)
* ci: auto-minimize comments from org-blocked users Adds a scheduled workflow that runs every hour to scan recent issue/PR comments and minimize any from users blocked at the org level. This cleans up spam comments that were posted before a block was applied. The workflow: 1. Fetches the org's blocked-user list via REST API 2. Queries recent comments (last 2h) via GraphQL 3. Matches comment authors against the blocked list 4. Minimizes unmatched comments as OFF_TOPIC via GraphQL Also triggerable manually via workflow_dispatch with a configurable lookback window. * ci: use repo blocklist file instead of org blocked-users API The org blocked-users API requires admin:org scope which the CI bot PAT doesn't have. Switch to a plain-text blocklist file at .github/spam-blocklist.txt — one username per line, case-insensitive, # for comments. No special scopes needed. Also adds danialzivehdadr as the first entry. * ci: make auto-minimize-spam failures visible, handle empty blocklist * fix(ci): address review feedback on auto-minimize-spam workflow (#7899) - Change hours input type from string to number (project convention) - Extract duplicated step-summary writes into write_summary() function - Capture gh stderr (2>&1) and include it in failure warnings - Document coverage limits in header comment * fix(ci): quote auto-minimize-spam expressions to pass yamllint (#7899) * test(ci): add regression guards for auto-minimize-spam workflow invariants (#7899) --------- 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> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> |
||
|
|
c55e63c42a
|
fix(release): bump preview base past published stable (#7978)
* fix(release): bump preview base past published stable When the nightly tag base (e.g. 0.21.0) is already published as stable, getPreviewVersion() now bumps the patch (→ 0.21.1-preview.0) instead of deriving a preview for the released version. This prevents the scheduled Tuesday preview release from hitting npm E403 on channel packages that were already published at that version. Fixes #7969 * fix(release): address preview version review feedback * fix(release): reject divergent preview baseline * fix(release): skip already published packages * test(release): cover publish skip guards * fix(release): bump preview past newer stable * fix(release): address review feedback on preview version guard - Expand doesVersionExist to check all 10 published packages instead of only @qwen-code/qwen-code, so the auto-increment loop detects versions taken on sibling channel packages. - Use the rollback-aware getAndVerifyTags lookup for the latest stable instead of the raw dist-tag, preventing a retrograde preview base when the dist-tag has been rolled back. - Emit :⚠️: in the channel publish loop when every package was already published, making a fully-skipped release visible. - Move preview stable-guard tests to Advanced Scenarios, add npmTag and previousReleaseTag assertions, add a non-bump boundary case, and assert the subshell wrapper and all-skipped warning in the workflow test. * test(release): cover channel package version conflicts --------- Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
2524157e12
|
fix(ci): restore workspace ownership before checkout in PR review job (#8062)
* fix(ci): restore workspace ownership before checkout in PR review job Self-hosted runners reuse the workspace across jobs. A prior containerised job (verify/tmux) may leave files owned by root/node or made read-only, which causes the checkout action to fail with EACCES while wiping the tree. Add a pre-checkout step that restores ownership to the runner user and makes the workspace writable, mirroring the cleanup already present in ci.yml and qwen-triage.yml. * fix(ci): use sudo fallback for review workspace restore * fix(ci): warn when workspace restore fails |
||
|
|
ff5c639e26
|
fix(release): raise model timeouts and shrink batch size for slow networks (#8007)
* fix(release): raise model timeouts and shrink batch size for slow networks The AI release-notes generator timed out on every batch in the v0.21.1 finalize run on a GitHub-hosted runner (US, Azure westus3) hitting a remote LLM endpoint. The 60s per-request timeout was too close to the edge: two successful requests took 54.2s and 56.7s, and every other batch hit the 60s abort. The 12-minute total budget was consumed by retries on those timeouts, and the circuit breaker opened after 3 consecutive failures, skipping highlights entirely. - timeoutMs: 60s -> 180s — give the model enough headroom to generate a full JSON response over a high-RTT cross-region connection. - totalTimeoutMs: 12min -> 30min — ~140 PRs at 8 per batch is ~18 batches; at ~90s each that's ~27 minutes of model time. - batchSize: 12 -> 8 — fewer entries per prompt means the model generates less per request, returning faster and reducing the chance of a single slow request dragging the whole batch. - workflow timeout-minutes: 15 -> 35 — match the new 30min budget plus a margin for git/npm setup. No changes to retry logic, circuit breaker, or prompt shape. * fix(release): sync timeout workflow test * test(release): assert step timeout exceeds script budget The workflow `timeout-minutes` and the script's `totalTimeoutMs` are defined in separate files with no shared constant. If they drift apart silently and the budget exceeds the step timeout, the runner SIGKILLs the step and even the fallback release notes are lost. Add a cross-file assertion so any future change to either value that breaks the invariant fails the test immediately. Suggested-by: wenshao in PR review. * test(release): parse release notes timeout expression |
||
|
|
ea4c25e25f
|
feat(triage): run external /verify on ECS behind a workspace wipe (#7985)
* feat(triage): sponsored /verify for external PRs on ephemeral runners
/verify used to require the PR AUTHOR to hold write access, because the
lane executes the author's code on the persistent ECS pool — which
excluded external contributors' PRs entirely (16 of 38 open PRs
eligible when measured on 2026-07-28), and those are the PRs where
sandboxed evidence is worth the most. The practical alternative was a
maintainer running the verification on their own machine, which is
strictly worse: personal credentials, SSH keys, and no isolation at
all. The risk was not avoided, only relocated.
The author's permission now ROUTES instead of gating, using the same
conditional runs-on pattern the authorize/triage jobs already use for
fork PRs:
- write-access author -> the persistent ECS pool, unchanged;
- external author -> a SPONSORED run on an ephemeral GitHub-hosted VM
(destroyed after the job), free on a public repository.
The commenter gate is unchanged: only a write-access maintainer can
trigger either lane, so runs stay maintainer-metered. Their comment is
the sponsorship, and authorize snapshots the head OID at that moment;
the resolve step refuses to run any other commit, so code pushed while
the job queues is never executed on the sponsor's approval. On the ECS
lane the execution-time re-check keeps its old meaning (the author's
write permission is the control there, and it can be revoked while
queued); its refusal now names the sponsored alternative.
Before anything executes on the hosted lane, a pre-execution risk
screen reads the PR diff as data: mechanical checks (added npm
lifecycle scripts, dependencies resolving outside registry.npmjs.org,
package-manager config changes, long opaque single-line content) and a
model screen, every arm failing CLOSED — an unfetchable diff, missing
screen config, a model error, and an unparseable reply each refuse the
run. Reasons posted to the PR are fixed strings chosen by the
workflow, never diff text, so a crafted diff cannot steer the bot's
comment. The screen is defense in depth, not the control: what bounds
a malicious PR is the ephemeral VM. The model can be fooled;
the machine being destroyed cannot.
The ECS kill switch no longer strands the hosted lane (an ephemeral
runner does not depend on the pool), and the authorize-side denial
notice for external authors is gone — the case it explained no longer
denies. The 2b-bis skill guidance now offers the sponsored run instead
of declaring the lanes unavailable, with an explicit warning that an
external author's report is adversarial input to be read like the
fork's own CI logs.
Mutation-verified 8/8, each with landing proof: dropping the commenter
gate, inverting the routing, dropping the OID snapshot, letting a
permission-API failure route instead of deny, dropping the head-moved
check, dropping the lifecycle-script check, tolerating an unparseable
model reply, and pinning the hosted lane back to ECS each turn a test
red. The screen tests run the real resolve-step text against a stubbed
gh and a live local HTTP server standing in for the model endpoint —
in a separate process, because spawnSync blocks the test runner's
event loop and an in-process server would deadlock into the fetch's
120 s abort instead of testing anything.
actionlint category diff vs main: -3 SC2016:info (the removed denial
step), +3 SC2129:style (new GITHUB_OUTPUT groups); both non-gating.
* docs(triage): persistence, not lateral movement, is the ECS threat
The maintainer confirmed the ECS runners are network-isolated, so the
comments justifying the hosted routing now name the decisive risk
correctly: machines are reused and the next run arrives carrying
credentials that planted artifacts could harvest. Lateral movement was
never the load-bearing argument; persistence is, and it is the one
threat a destroyed VM removes without depending on sweep completeness.
* feat(triage): run external /verify on ECS behind a workspace wipe
Maintainer decision (2026-07-29): external contributors' PRs run on the
persistent ECS pool like everyone else, not on ephemeral GitHub-hosted
VMs. The pool is network-isolated, and the realistic alternative — a
maintainer verifying on a personal machine with real credentials and no
isolation — is worse than a hardened shared runner.
That removes machine destruction from the design, so the author's
permission now selects CONTROLS rather than a machine:
trusted -> unchanged: write permission is the control, re-checked at
execution time, head may move freely.
external -> pinned head OID (the sponsoring comment approves the code
that existed then), pre-execution risk screen, and a full
workspace wipe.
The wipe is what replaces destruction. The residual ECS risk for
untrusted code is PERSISTENCE: machines are reused, and the next run
starts by handling credentials (CI_BOT_PAT in the resolve step, the
model key in the proxy). The existing sweep is an allowlist of vectors
we have thought of, and an adversarial author gets unlimited attempts to
find one we have not — so external runs delete the workspace outright
before checkout instead of sweeping it. Deny-by-default costs one cold
clone and removes hooks, worktree config, planted dotfiles, staged
artifacts and out-of-tree symlinks in one move, known or not. Scope is
stated in the step: RUNNER_TEMP and /tmp also persist, and the steps
reading them already rm -rf their own subtrees; the container is
recreated per job, so /home/node and its npm cache are not cross-run
state.
The screen's status changes with the machine: on ephemeral hardware it
was a cost-saver, here it is load-bearing — but still not sufficient
alone, and the comment says so, naming the wipe and the sweep as what
actually bounds a screened-but-hostile diff.
Mutation-verified 6/6: removing the wipe, moving it after checkout,
applying it to every run, tolerating a non-empty workspace, dropping the
suspicious-path guard, and classifying an external author as trusted
each turn a test red.
Also fixes a live hazard in the guard's own test. It passed real paths
(/, /usr, /root) to a live `rm -rf`, which is safe only while the guard
exists — so it detonates precisely when the guard is removed, which is
when it is meant to protect you. It did: a mutation run that deleted the
guard spent six minutes attempting to delete / before being killed.
macOS permissions absorbed it and nothing was lost, but that outcome was
luck, not design. `rm` is now stubbed to a recorder on PATH, so the
destructive primitive cannot fire from the test suite under any edit,
and the assertion is on the decision — with the guard gone the recorder
shows the attempted delete and the test fails, having deleted nothing.
* fix(triage): close two screen bypasses and a vacuous routing assertion
Review round on #7985 found four issues; all four were real.
1. npm lifecycle alternation stopped at the install lifecycle, but npm
runs pre/post hooks for ANY `npm run <script>`. This job runs
`npm run build` and the agent runs the PR's suites, so a `prebuild`
or `pretest` executes exactly like a `postinstall` and walked past
the mechanical screen. Added build and test to the alternation.
2. The off-registry check excluded any line CONTAINING
`registry.npmjs.org`, so a lookalike host — registry.npmjs.org.evil.com,
or an @-userinfo form — read as npmjs and passed. The tarball's own
postinstall lives inside the tarball, never in the diff, so this arm
was the only mechanical defense against it. The exclusion is now
anchored to scheme + host + '/'.
3. A routing assertion read `.runner`, a key from the earlier
ephemeral-VM design that `gate()` never returns, so it was
`expect(undefined).toBeUndefined()` — green no matter how badly the
trust level leaked into /triage or /tmux. Asserts the real keys now,
on both non-verify paths.
4. Three screen arms had no scenario at all: package-manager config,
the long-opaque-line awk, and the unconfigured-model fail-closed
path. All three are covered, each with a negative control so the arm
is not merely refusing everything — the genuine registry URL and a
700-char lockfile integrity line must still pass.
Mutation-verified 6/6, each failure naming the payload it let through:
reverting the alternation (`prebuild was not flagged`), un-anchoring the
registry match (`registry.npmjs.org.evil.com was not flagged`), breaking
the package-manager grep (`.yarnrc was not flagged`), raising the awk
threshold, letting the unconfigured path fall through to clear, and
leaking verify_trust into /triage — which is exactly what the vacuous
assertion could never see.
93/93 tests; prettier, eslint and shellcheck clean.
* fix(triage): close post-run persistence gap and screen bypasses (#7985)
Maintainer review found the wipe only ran BEFORE external code, leaving
the next pool job to meet the allowlist sweep alone. Add a post-run wipe
with `if: always()` so a cancelled or timed-out external job still
cleans up.
The model screen silently truncated at 200 KB — a fail-open in a
fail-closed step. Refuse oversized diffs mechanically before the model
call.
The screen prompt still described an "ephemeral, credential-free
sandbox" from the deleted ephemeral-runner design, calibrating the
model for leniency the persistent pool does not warrant. Describe the
actual environment. Also fix the stale "hosted lane" comment.
Three cheap mechanical-screen hardenings from the review: drop the
root anchor on the package-manager config grep (a nested .npmrc is
just as dangerous), match `rename to` headers (a rename-only hunk
emits no `+++ b/` line), and relax the opaque-line awk from
"no space in the line" to "no space-free field >= 600" so a single
space anywhere is no longer a bypass.
94/94 tests; build, typecheck, lint clean.
* test(triage): cover the unfetchable-diff fail-closed screen guard (#7985)
---------
Co-authored-by: wenshao <wenshao@example.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
|
||
|
|
0c5a4deabd
|
test(integration): migrate flaky E2E tests to fake-openai-server (#7934)
* test(integration): migrate flaky E2E tests to fake-openai-server Migrate 39 real-model test cases to use deterministic fake-openai-server scripting, eliminating model output variance as a failure source. - tool-control.test.ts: 23 cases migrated (tool filtering, permission denial, canUseTool routing, priority rules) - abort-and-lifecycle.test.ts: 15 cases migrated (abort mechanics, lifecycle, cleanup, debug output) - list_directory.test.ts: 1 case migrated (CLI TestRig with env injection) channel-plugin.test.ts (3 cases) is intentionally left on real model — it tests the full WebSocket→AcpBridge→model pipeline and belongs in the nightly smoke layer per #7616. Closes #7616 * test(ci): move channel-plugin to nightly + relax assertions channel-plugin.test.ts tests the full WebSocket→AcpBridge→model pipeline with real model inference. Its assertions on specific model output ('4', 'pineapple', '50') are inherently non-deterministic. - Exclude from post-merge E2E jobs (Linux + macOS) - Add dedicated nightly-only job with continue-on-error - Relax assertions: verify response is non-empty rather than matching specific model output content - Add retry: 2 to each test case * fix(test): use streaming chunks for abort test, restore comment - abort-and-lifecycle 'should handle abort during query execution': switch from non-streaming content to contentChunks so the abort signal reliably lands while the response is still streaming - channel-plugin: restore existing comment per AGENTS.md convention * test(integration): address fake server review feedback * test(integration): trim fake server review assertions * test(integration): tighten fake server cleanup * test(integration): fix permanently-failing stdin-close case and harden list_directory against user settings The stdin-close case's fake server needle contained a raw quote that JSON.stringify-escaped transcripts can never match, and the scripted write_file was rejected by prior-read enforcement; the script now keys off a quote-free marker and reads before writing. list_directory now pins auth via CLI flags so a developer's ~/.qwen/settings.json cannot silently route the run to a real model endpoint. * test(integration): tighten fake server review regressions * test(integration): guard abort assertions * test(integration): fix abort timer race and dead coreTools assertions * test(integration): address fake server review findings * test(integration): isolate fake fast model settings * test(e2e): simplify isolated test setup --------- 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: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
d0481ad884
|
feat(triage): lead the verify comment with a qualitative verdict, fold the Chinese (#7974)
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 / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
* feat(triage): lead the verify comment with a qualitative verdict, fold the Chinese Maintainer feedback on the first 14-run day of the lane, pointing at the report on PR #7836: give a plain pass/no-pass, and make the layout English-by-default with the Chinese folded (the repo's PR-body convention), instead of doubling every head line. Three changes: - every headline leads with a qualitative call — ✅ passed / ❌ not passed / ⚠️ inconclusive / ⚠️ incomplete — ahead of the lane taxonomy. The mapping is honest about what each outcome can claim: only agent verdicts judge the code; a crashed, timed-out, or verdict-less run renders as incomplete/inconclusive, never as a pass or a fail, and a test pins that no process-outcome arm carries ✅ or ❌. - the Chinese version folds into one <details> whose SUMMARY carries the qualitative verdict — collapsed, a Chinese reader still sees 通过/不通过 in one line. English scope and assertion count stay unfolded; both language assertion lines are built from the same validated triple, so inconsistent JSON still suppresses both. - the #7836 root cause gets a Hard rule in the verify-pr skill. That report said "merge-ready — the 7 failures are all expected A/B base-cell failures proving the tests load-bearing" while assertions.json said fail:7, so the publisher's trust rule (merge-ready requires fail==0) correctly refused it and the headline degraded to "no usable structured verdict". Both sides told the truth about different questions. The rule fixes the semantics: an A/B control cell is an assertion that the base arm FAILS — when it does, that assertion PASSED. fail counts only unexpected outcomes, which is what makes a qualitative headline trustworthy at a glance. Mutation-verified 6/6: dropping the qualitative prefix, collapsing all Chinese quals onto one string, unfolding the Chinese scope, giving timeout a ✅, dropping the folded Chinese assertion line, and deleting the new Hard rule each turn at least one test red. The fifth mutation initially reported "landed: False" — the regex never matched and the file was untouched, so the green result proved nothing; re-applied as a line filter with a removed-block count, it kills two tests. One new shellcheck SC2016 info finding (backticks inside a single-quoted Chinese printf), same shape as the 11 pre-existing ones; info-level, does not gate. * test(triage): pin the dropped verdict arms and prepare-failure glyphs (#7974) Restore rendering coverage for the reachable infra-error and unknown catch-all case arms in the qualitative-headline bijection, and assert the prepare-failure path's qualitative glyphs, Chinese fold summaries, and the Chinese retry clause so a swapped emoji or dropped PREPARE_ATTEMPTS_ZH interpolation can no longer ship undetected. * fix(triage): migrate all weak headlines, explain merge-ready mismatch (#7974) * fix(triage): fold Chinese mismatch explanation, differentiate distrust warnings (#7974) --------- Co-authored-by: wenshao <wenshao@example.com> Co-authored-by: Qwen Code CI Bot <qwen-code-ci-bot@users.noreply.github.com> |
||
|
|
5daf75197e
|
ci: add Windows runner smoke test (#8008)
* ci: add Windows runner smoke test * ci: use built-in Windows PowerShell * ci: secure Windows runner smoke trigger * ci: run full test suite in Windows validation * ci: keep Windows validation manual |
||
|
|
324b5fba38
|
fix(release): skip notes-start-tag when previous release diverges from target (#7969) (#7970)
* fix(release): skip notes-start-tag when previous release diverges from target (#7969) * style(release): fix prettier formatting in release workflow (#7969) * fix(release): warn when notes-start-tag is skipped for a divergent tag (#7969) --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> |
||
|
|
1b5c36ce15
|
ci: add isolated DSW SWE-bench release pipeline (#7656)
* ci: add isolated DSW SWE release pipeline
* ci: bootstrap branch-only DSW full-suite test
* Revert "ci: bootstrap branch-only DSW full-suite test"
This reverts commit
|
||
|
|
6b9b861398
|
fix(ci): update the Qwen binary used by ECS runners (#8000)
* fix(ci): install Qwen into system npm prefix * test(ci): update ECS runner workflow guard |
||
|
|
15ca9818e0
|
fix(ci): restore verify workspace permissions (#7992) | ||
|
|
a7b1150816
|
fix(ci): clean stale .qwen/ before checkout to prevent Permission denied (#7977)
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 / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
On reused GitHub-hosted runners, a previous job (e.g. verify/tmux on the same runner pool) can leave .qwen/ files with restrictive permissions (chmod -R a-w). The next job's checkout step then fails with "error: unable to unlink old '.qwen/...': Permission denied" because git cannot delete the read-only files during workspace cleanup. Add a pre-checkout step that chmods .qwen/ writable and removes it before the checkout action runs. This is a no-op on fresh runners where .qwen/ doesn't exist yet. Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: 易良 <1204183885@qq.com> |
||
|
|
0c0ca5fed0
|
feat(autofix): defer suggestions after five change rounds (#7913)
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 / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
* feat(autofix): defer suggestions after five change rounds * test(autofix): execute deferred jq queries against fixture data (#7913) * fix(autofix): scrub control markers from deferred feedback reports (#7913) * test(autofix): execute actionable reviews and issue-level filters against fixtures (#7913) --------- Co-authored-by: qwen-code-dev-bot <269191875+qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
5b6714b9fb
|
fix(ci): give each job its own proxy wrapper directory (#7951)
The PR review job and the containerised triage jobs all built their gh/git proxy wrappers in one fixed directory under RUNNER_TEMP. On the shared self-hosted runner that path outlives a job, and the triage containers write it as root through the RUNNER_TEMP bind mount. Once that happened, the review job — running as the unprivileged runner user — could neither overwrite the wrapper nor remove the root-owned directory holding it, so every review scheduled on that runner died with EACCES while writing the wrapper, before the review itself had started. Each run now creates a private wrapper directory and removes it on exit, and the container jobs keep theirs on the container's own disk so they stop leaving root-owned state on the host. |
||
|
|
ddf4b8875d
|
fix(triage): make the build-process guard diagnosable and zombie-aware (#7858)
* fix(triage): make the build-process guard diagnosable and zombie-aware
The first real /verify run to reach the agent step (job 30267953352) was
stopped by the guard I added to stop detached lifecycle children from
outliving their step:
::error::Processes owned by the build user survived; refusing to start
the agent.
That is all it printed. No pid, no state, no command line — so a genuine
threat and a harmless leftover were indistinguishable, including to the
person who wrote it. The control had no observability of its own.
Two changes, both in the verify and tmux lanes:
- name the survivors. The error now lists each one as pid, state and
command line, so the next occurrence can actually be diagnosed.
- disregard zombies. A zombie has already exited and released everything
except its exit status: it cannot re-plant an artifact or touch the
agent's inputs, and it cannot be killed either — so counting one means
the check can never clear, no matter how many times SIGKILL is sent.
The container runs no init to reap orphans, so they are expected here.
The platform difference is the reason the old form could hang: Linux
pgrep reports defunct processes ("Defunct processes are reported." —
pgrep(1), procps-ng), while macOS pgrep does not list them at all
(verified directly: ps shows our zombie, pgrep does not). So this failure
mode was unreachable in local replay and only appears on the runner.
That same difference shapes the tests. The behavioural arm constructs a
real zombie, confirms it survives SIGKILL, and shows the state filter
drops it — but it cannot discriminate the old implementation from the new
one on macOS, because pgrep never saw the zombie there in the first place.
So the portable guarantee is asserted structurally: the filter must read
process state and exclude Z, and must not be a bare pgrep. Mutation-
verified 3/3 — removing the state filter, dropping the survivor names, or
reverting the message each turn one test red.
Also gives both proxy-watchdog tests an explicit timeout. They stream 20
chunks at 200 ms — 4 s before the stall arm even starts — so they cannot
fit vitest's 5 s default and were timing out on main.
* fix(triage): tolerate zero-process exit in build guard (#7858)
---------
Co-authored-by: wenshao <wenshao@example.com>
Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
|
||
|
|
25e357a279
|
feat(triage): make the verify report readable in Chinese (#7918)
* feat(triage): make the verify report readable in Chinese
The scope disclaimer under the verify headline has been bilingual since
the lane shipped. The verdict above it was not, and neither was the
assertion count — so a Chinese reader of the unfolded comment got the
caveat ("advisory evidence, not a review") and not the conclusion.
Three changes, all on the parts a reader reaches first:
- every headline carries a Chinese twin, across all eight arms —
merge-ready / findings / blocked / inconclusive from the agent, and
completed / fail / timeout / infra-error from the process outcome;
- the assertion count renders in both languages, off the same validated
object, so an inconsistent assertions.json still suppresses both and
the comment cannot grow a number no gate checked;
- in the report itself, 中文摘要 moves from last item to second, right
after the verdict. The whole report is already inside a <details> on
the PR, so leaving the Chinese summary at the bottom meant expanding
that fold and scrolling the entire English report — about 90 lines on
a real one — to reach the one section written for that reader. It
stays collapsed, so it costs everyone else exactly one line.
The headline test pins the pairing as a bijection, not as containment:
asserting only that each arm renders some Chinese leaves a single
hardcoded string — or one that echoes the English — passing every arm.
Mutation-verified 4/4: dropping the Chinese headline, collapsing all
arms onto one Chinese string, dropping the Chinese assertion sentence,
and moving 中文摘要 back to the end each turn one test red.
* fix(ci): correct cross-reference direction and cover all verdict branches in bilingual headline test (#7918)
* fix(ci): narrow bilingual headline comment to match actual scope (#7918)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
---------
Co-authored-by: wenshao <wenshao@example.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
|
||
|
|
c7de1d7b7c
|
perf(ci): cut the E2E suite from ~40min to ~24min (#7798)
* perf(ci): shard the E2E suite and stop building it twice per job A full E2E run took about 40 minutes, twice the median gap between merges on main, so the post-merge suite could never keep up with the merge rate. Two thirds of that was avoidable work. Every job installed dependencies with the default `prepare` behaviour, which builds and bundles the whole workspace, and then ran explicit build and bundle steps that did it all a second time — about 3.5 minutes on Linux and 5 on macOS. The docker leg additionally let the sandbox script re-run install, build, bundle and pack on the host before building the image, none of which the image consumes: its Dockerfile rebuilds and packs inside its own builder stage. The remaining cost is the test run itself, which is now split across shards: three for each Linux sandbox mode and two for macOS. Vitest assigns files to shards by path hash, so the long tail of slow sdk-typescript files spreads out rather than clustering in one shard; replaying the last green run's per-file timings through that algorithm puts the slowest shard at about 6 minutes against the 16 to 25 minutes each leg used to spend. The matrix no longer fails fast either, because a cancelled sibling shard would hide most of the suite and report an incomplete failure set. Expected wall clock per run drops from about 40 minutes to about 24, with the sandbox image build — 13 minutes, now paid once per docker shard — as the largest remaining item and the obvious next target via Docker layer caching. * fix(ci): set QWEN_SANDBOX=docker on the sandbox image build step (#7798) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(ci): quote the sandbox env value so yamllint passes The value added for the image build step was unquoted, which the repository's quoted-strings rule rejects, and note why the variable is needed at all. * fix(ci): stream docker build output in the sandbox image step (#7798) * fix(ci): skip duplicate build in sandbox image npm ci (#7798) The Dockerfile builder stage runs npm ci followed by explicit build and bundle steps. npm ci triggers the prepare script, which builds and bundles the whole workspace — duplicating the two steps that follow. Set QWEN_SKIP_PREPARE=1 on the npm ci so prepare only generates git-commit.ts, matching the same fix already applied to the host CI install steps. --------- Co-authored-by: Qwen Code <qwen-code@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> Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com> |
||
|
|
ce55360958
|
fix(ci): restore workspace ownership in cleanup to prevent EACCES (#7931)
The verify and tmux jobs chown the workspace to node:node for the build step, but the end-of-job cleanup never restores ownership. On self-hosted runners, the next job's checkout step (running as the runner user) fails with EACCES when trying to delete node-owned files in .qwen/ and node_modules/. Add a chown back to the runner user (detected via stat on $RUNNER_TEMP) at the end of both "Clean up runner workspace" steps. Only runs when the runner UID is non-root (i.e. on self-hosted runners where the runner user differs from root). 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> |
||
|
|
43c35e533b
|
fix(ci): keep the post-merge E2E signal on main alive (#7795)
* fix(ci): stop cancelling in-progress E2E runs on main Merges land on main roughly every 18 minutes (median) while a full E2E run takes about 40, so cancelling the in-flight run on every push starved the suite: across the last 100 push runs, 67 were cancelled and only 25 ever reported a result. The nightly regression was the only reliable signal, and each cancelled run still burned roughly 10 minutes on three runners before dying. Turning cancellation off for main hands the coalescing to GitHub's concurrency queue, which keeps at most one pending run per group and cancels the previously pending one. The queue therefore collapses to the newest tree on its own while the in-flight run always finishes, so every result covers the batch of commits merged since the last one — bisect that range when it goes red. Dev branches keep cancelling superseded runs, where only the latest push matters. * fix(ci): dedupe main CI failure issues by failing test The autofix issue for a red main was deduped on the commit SHA, so a standing failure opened a brand new issue on every merge: one broken E2E test on 2026-07-26 produced six duplicate issues in twelve hours, each pointing the autofix agent at an unrelated commit. The failing tests are now identified from the logs of the failed jobs and used as the dedupe key, with one marker per test so a failure set that grows still matches the issue that already tracks part of it. Later commits hitting the same failure are appended to that issue as recurrences (bounded, newest first) instead of opening another one, and notes written by a human or the agent are preserved when the machine-owned trailer is refreshed. Runs with no identifiable test — an install or build break — keep the previous per-commit behaviour. * fix(ci): keep the failure analysis out of the bot-PAT job Identifying the failing tests means running a helper from the repository, and the workflow deliberately checked out nothing so that the job holding the bot PAT could never execute repository code — an invariant its own test enforces. Rather than weaken it, the work is split: a read-only job checks out the tree, reads the failed run's logs, finds any issue that already tracks the failure and renders the title and body, handing both over as outputs. The job with the PAT keeps checking out nothing and only writes what it was given. The invariant test now asserts that separation — the PAT job runs no repository code and holds only issue write — instead of banning checkout everywhere in the file. * fix(ci): harden main CI failure dedupe per review (#7795) - Match the `[run <id>]` link text instead of the run URL when deduping recurrences: `/301` is a substring of `/3010`, so the URL match silently deleted an unrelated run's line. - Rebuild the machine-owned "## Also failing" section from the live failure set on every merge so a test that has since been fixed drops out instead of being listed forever. - Assert the analyze checkout is SHA-pinned with persist-credentials disabled and pin the failed-job log-download paths in the workflow test. - Add an e2e workflow test guarding the cancel-in-progress expression that keeps in-progress runs on main from being cancelled. * fix(ci): bound the issue body and randomize the heredoc delimiter (#7795) * fix(ci): test the runCli --existing merge path (#7795) * fix(ci): address review feedback on e2e signal PR (#7795) - Assert the full cancel-in-progress expression including && so a mutation to || is caught by the e2e-workflow test - Filter the capped-summary line from missingTests so it is not rendered as a fake bullet under Also failing --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com> |
||
|
|
45a6a69cf0
|
feat(triage): add revert-pattern high-risk path detection (#7414)
* feat(triage): add revert-pattern high-risk path detection Replace the behavior-neutral PR filter (PR #7414 v1, ~2% hit rate) with a data-backed triage gate based on revert-history analysis of 111 revert commits and 46 unique reverted PRs in this repo. Stage 1e checks three signals identified by the analysis: - touches_high_risk (66.7% precision, 32.3% recall) - contested-merge pattern (50.0% precision, 19.4% recall) - non-maintainer + high-risk (58.3% precision, 22.6% recall) The gate escalates review depth and recommends maintainer sign-off; it never blocks or closes PRs. Design doc and analysis scripts included. * fix(triage): avoid stale-exempt hold label * fix(triage): address review risk detection feedback * fix(triage): tighten high-risk path patterns * fix(triage): address review feedback on Stage 1e revert-pattern gate (#7414) * fix(triage): address round-2 review feedback on Stage 1e gate (#7414) - Fix APPROVE → APPROVED state name (GitHub API enum) - Use gh api --paginate for file listing (fixes 100-file truncation) - Anchor shell/relaunch/sandbox patterns with (^|/) to avoid false positives - Append || true to grep (exit 1 on no match is the 92% case) - Scope E2E recommendation to write-access authors per Stage 2c - Add bot author filter to contested-merge query - Define core paths explicitly in contested-merge condition - Wire Stage 1e do-not-auto-approve into Stage 3 guardrail - Replace precision percentages with p-values/raw counts in skill text - Add sampling caveat and statistical significance notes to design doc - Fix design doc errors: 71%→61.5%, 10→8 PRs, Rule 3 attribution, ~20% baseline→10% prevalence, Area field, no_e2e inconsistency - Make test assertions specific to Stage 1e (not vacuous) - Add Risk: template field assertion - Revert drive-by prettier reflow - Note need-discussion label removal by maintainer * fix(triage): address round-3 review feedback on Stage 1e gate (#7414) - Separate gh api call from grep so API failures are visible instead of being masked by || true (rc:3660753982) - Include author identity in contested-merge jq output and require different reviewers for the disagreement check, avoiding false positives from same-reviewer iteration (rc:3660753990) - Add Stage 1e to the approval summary checklist so it is not omitted from the pre-approval conditions (rc:3660753994) * fix(triage): address round-4 review feedback on Stage 1e gate (#7414) * fix(triage): use portable ERE grep for test-file exclusion (#7414) * fix(triage): guard deferred approval on discussion label * fix(triage): keep only supported revert signal --------- Co-authored-by: qwen-code-bot <qwen-code-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> |
||
|
|
47118be482
|
feat(ci): Deduplicate E2E failure issues by commenting on existing issue (#7792)
* feat(ci): deduplicate E2E failure issues by commenting on existing issue * fix(ci): address failure issue review feedback * test(ci): cover CI issue dedup invariants * fix(ci): include comments in failure marker dedup * fix(ci): include issue comments in failure marker search * fix(ci): share failure issue title prefix * fix(ci): filter dedup issue titles * test(ci): assert SHA dedup runs before workflow-name dedup (#7792) * fix(ci): limit SHA dedup search to issue bodies * fix(ci): verify bot ownership before reusing title-matched issue (#7792) --------- Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
ec62797c1c
|
fix(review): give the review retry the remaining time budget (#7852)
* fix(review): give the review retry the remaining time budget A transient API failure on the first review attempt was retried with a hardcoded 5-minute timeout while the first attempt got the whole 180-minute budget. The retry does not resume the failed review, it re-runs it from scratch, so on a large PR it spends those five minutes re-fetching the PR worktree, re-chunking the diff and re-launching review agents and is then killed on the clock before producing a single finding. Every retry after a transient failure therefore reported a timeout, and the resulting comment advised raising --timeout, which does not affect the retry cap at all. All attempts already share the single-review budget, so that budget is the only bound the retry needs. Give every attempt the remaining budget and gate the retry on having enough of it left to plausibly finish; below that, report the transient failure so the next run starts over with a full budget. * test(review): pin the per-attempt review timeout budget The retry-loop harness stubbed timeout with `shift; shift; exec "$@"`, which discarded the duration argument, so no test could observe how much budget each attempt was given. That is the invariant the retry fix turns on: a reintroduced per-attempt cap, or a miscalculated remaining budget, would have left the suite green while making every retry unusable. Record the duration the stub receives and assert on it. Cover the retry gate from both sides as well, since every existing scenario runs with a 180-minute budget and none of them exercise the threshold: a budget just under it must not start a retry that cannot finish, and one just over it must still retry. |
||
|
|
203093c53f
|
fix(triage): retry a transient npm ci before blaming the PR for it (#7884)
Run 30319209722 posted `Sandboxed verification: fail` on PR #7856 with "The PR could not be built ... treated as a PR failure verdict rather than an infrastructure failure." The PR changed six source files. The install died here: Error: spawnSync .../web-templates/node_modules/esbuild/bin/esbuild ETXTBSY at validateBinaryVersion (esbuild/install.js:99:28) ETXTBSY is npm writing a dependency's binary and that package's own install script exec'ing it before the write is closed — a race, and one the PR had no part in. The comment accused its author anyway. Both sandbox lanes now retry `npm ci` once. `npm ci` removes node_modules before installing, so the second attempt cannot inherit the half-written file; the transient class is simply absorbed instead of being classified. Retrying rather than classifying is the point. The obvious fix — match ETXTBSY in the log and downgrade to infra-error — would read text the PR's own lifecycle scripts can print, which is exactly the forgeable signal the verdict logic was rewritten to stop consulting: it let a PR launder its own deterministic breakage into "infrastructure, please re-run". A retry consults nothing. A genuinely broken tree fails twice and still earns `fail`, and the second attempt is only ever paid on a path that is already failing. The build is deliberately not retried: a compile error is deterministic, so a second run would only double the cost of an honest failure. Because the install now gets two chances, the sentence that blames the PR says so — "failed twice in a row". PREPARE_ATTEMPTS is initialised rather than defaulted at the point of use, or an inherited value would claim a single-shot `npm run build` had failed twice, which is the same false accusation pointed the other way. Mutation-verified 5/5: an unbounded retry, no retry at all, a retry on the build, dropping the initialisation, and reverting the wording each turn at least one test red. The retry itself is covered behaviourally — the real step text runs against a stubbed `runuser`/`npm`, so the tests count actual install attempts instead of asserting that a loop exists, which would pass on both a loop that never retries and one that never stops. Co-authored-by: wenshao <wenshao@example.com> |
||
|
|
083f5b97d0
|
fix(release): publish all channel packages, not just channel-base (#7845)
* fix(release): publish all channel packages, not just channel-base The release workflow only published @qwen-code/channel-base while the other 7 channel packages (dingtalk, feishu, github, qqbot, telegram, wecom, weixin) were never published to npm despite being non-private and version-synced. Add a loop step after channel-base that publishes all remaining channel packages with the same npm tag and dry-run support. * chore(release): record channel preview packages * chore(release): align channel preview versions * chore(release): restore channel source versions * chore(channels): emit declaration maps |
||
|
|
c994524a2d
|
feat(autofix): retry deterministic rejection once (#7796)
* feat(autofix): retry deterministic rejection once * fix(autofix): use repaired verification result * fix(autofix): preserve repair safety context * test(autofix): cover empty verification outcomes |
||
|
|
ef69eff20c
|
fix(ci): prevent worktree cleanup from failing the verify job (#7857)
The `*) [ -n "$wt_abs" ] && echo ...` pattern in the worktree cleanup loop exits 1 when a stale worktree entry has a missing directory (cd fails → wt_abs is empty → `[ -n "" ]` returns 1). Because the while loop is the last pipeline component under `set -e` + `pipefail`, this kills the entire step — contradicting the "never fail the job" intent and skipping the actual verification. Replace the `&&` compound with an `if` statement, which exits 0 when the condition is false. Fixes the "Clean stale agent state" and "Clean up runner workspace" failures seen in run 30273453000 (job 90001522381). Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
ad2252401b
|
ci(release): comment released-in version on merged PRs (#7814)
* ci(release): comment released-in version on merged PRs After a stable release is published, comment on each merged PR in the release range with a link to the release tag, so contributors can find which version shipped their change directly from the PR timeline. Collects PR numbers from squash-merge commit subjects between the previous and current stable tags, posts a marker-tagged comment, and skips PRs that already carry the marker (re-run safety). Nightly and preview releases are out of scope, since finalize only handles stable tags. * fix(ci): narrow release PR extraction * test(ci): cover release comment safety checks |