mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-06 23:35:34 +00:00
543 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
732f4d8a29
|
feat(voice): support trusted private ASR base URLs (#8350)
* feat(voice): support trusted private ASR base URLs * fix(voice): address private endpoint review findings * test(voice): cover private endpoint edge cases * test(voice): pin remaining endpoint edge cases * fix(voice): address private endpoint review feedback * fix(voice): clarify allowlist URL and normalize IPv6 * fix(voice): harden NAT64 address validation * fix(voice): address managed endpoint review findings * refactor(voice): extract shared IPv6 transition unwrap ladder (#8350) Deduplicate the IPv6-transition unwrapping sequence (mapped, compatible, NAT64, dotted-quad) that was repeated verbatim between isPrivateNetworkIp and isAlwaysBlockedVoiceAddress on both CLI and Desktop surfaces. A single unwrapIpv6TransitionStep helper now yields the next canonical address (or 'blocked' for unrecognized ::ffff: forms), and each predicate recurses through it, preserving the exact re-check semantics at every unwrap level. * test(voice): cover allowInsecureBaseUrl wiring through desktop default transports (#8350) * fix(voice): add allowlist hint to private-network rejection error (#8350) * fix(voice): reject always-blocked base URLs before offering the allowlist hint (#8350) * fix(voice): resolve exact desktop voice provider before OAuth (#8350) * fix(voice): address review feedback for trusted private base URLs (#8350) * fix(voice): align desktop voice resolution with CLI semantics (#8350) * fix(voice): scope desktop fail-closed resolution to policy-bearing entries (#8350) * fix(voice): address round-8 review findings for trusted private base URLs (#8350) Run the invasive process-global `mock.module('ws')` suite as voice-ws-handler.isolated.ts so the desktop package's single-process `bun test` run no longer leaks the fake socket into unrelated ws consumers; the existing isolated loop runs it in its own process. Shape-guard the desktop provider scan: non-object modelProviders elements are skipped (falling through to OAuth instead of throwing a raw TypeError), and non-string baseUrl/envKey/settings.env values on a voice-model entry now surface the PROVIDER_ENTRY_REMEDY remediation error instead of crashing. Compute the DashScope-compatible /v1 rewrite before any allowlist match in fromExactModelProvider so the stage-1 check, the remediation messages, and the top-level recheck all compare the same final URL and a single allowlist entry converges for split-horizon deployments. Extend the CLI allowlist remediation messages to state which settings scopes honor the entry, since serve mode never shows the interactive workspace-strip warning. Thread providerProtocol through the CLI voice model seams (createVoiceModelSource and the daemon buildModelsConfig) so protocol-mapped custom provider groups resolve like the rest of the CLI model surface, and document the remaining protocol-agnostic desktop scan in the design doc. Correct the getHomeEnvFallback comment: it adopts the narrower getHomeEnvFallbackVars candidate set on purpose. Add multi-record DNS answer tests on both CLI and desktop net guards so the records.some classification is pinned against the array shape defaultLookupHost always produces in production. * fix(voice): address round-9 review findings for trusted private base URLs (#8350) * fix(voice): address round-10 review findings for trusted private base URLs (#8350) * fix(voice): classify desktop voice duplicates before ambiguity check (#8350) * fix(scripts): compare voice guard mirrors as parse trees (#8350) --------- Co-authored-by: rockybot2026 <265985139+rockybot2026@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> 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> |
||
|
|
35108c0cf0
|
ci(autofix): run heavy autofix jobs on the ECS pool with hosted fallback (#8603)
* ci(autofix): run heavy autofix jobs on the ECS pool with hosted fallback
The heavy autofix jobs (issue-autofix, build-cli, review-address) were
pinned to GitHub-hosted runners, where they hold up to five 300-minute
slots per scan. Combined with CI, that saturates the org's ~20 hosted
concurrency slots and starves the PR-review workflow's hosted-only jobs
(delay timer, fork authorize, /resolve).
Route the three heavy jobs to the persistent ecs-qwen pool using the same
repository-guarded expression as the rest of the fleet, with an
ubuntu-latest fallback for forks of this repo and for
MAINTAINER_ECS_RUNNER_DISABLED.
This is safe for the fleet's two secret-bearing agent jobs:
- Every review-address target is live-gated to write+ (internal) authors
at scan AND address time; in-repo PR heads already require push access.
- CI_DEV_BOT_PAT is a write-level branch-push token with no privilege
escalation (cannot bypass branch protection).
- The ECS pool ships docker, and repo-hygiene already runs the sandbox
image there in production; sandbox containers use --rm with no fixed
name, so concurrent legs on one machine do not collide.
Relax the 'Check runner environment' gate to accept self-hosted, and drop
the github-hosted condition on 'Set up Node.js' so Node 22 is provisioned
on ECS. The contract test is updated to pin the new ECS routing expression
(including the repository guard and hosted fallback) instead of the old
hosted-only one.
* ci(autofix): harden ECS-routed heavy jobs for the shared persistent pool
- Restore workspace ownership before checkout in issue-autofix,
build-cli, and review-address, matching the sibling pool workflows
(root-owned leftovers from containerised jobs break checkout/npm ci).
- Sanitize the reused workspace's local git config (allowlisted
unset-all + symlink-aware hook sweep, qwen-triage's pattern) before
the PAT-bearing git steps in issue-autofix and review-address.
- Give issue-autofix a per-run WORKDIR (/tmp/autofix-<run_id>): pool
registrations share one /tmp and issue-phase runs never serialize.
- Drop stale local autofix/issue-* branches after checkout so a retry
on the same registration cannot hit "branch already exists" or push
a failed attempt's stale commits.
- Extend the ECS routing guard for PR-family events (same-repo head or
write+ author), mirroring ci.yml's classify_pr convention, and fix
the runs-on comments that claimed fork PRs already fell back.
- Stop persisting CI_DEV_BOT_PAT into the origin remote URL: publish
pushes authenticate transiently, and the redundant set-url in
'Push and report' is removed (push/fetch already use a URL inline).
- Pin all of the above in the workflow contract tests, reusing hoisted
job-block constants.
* ci(autofix): harden ECS pool hygiene with shared hardened sanitize action
Review round for the ECS routing of the heavy autofix jobs. The
sanitize step shipped in the previous commit was the trusted-lane
variant of the qwen-triage sweep, which two verified bypasses defeat
(worktree-scoped config carrying core.hooksPath, and a global
core.hooksPath steering the hook deletion). Port the hardened variant
into a composite action so all three heavy jobs share one copy, and
add it to build-cli, whose checkout runs in the same reused workspace
and feeds the bundle every PAT-bearing leg executes.
Also completes the persistent-pool hygiene the routing depends on:
per-run/per-target WORKDIRs now chmod 700 at creation and are removed
by an always() teardown step after the artifact upload (nothing else
ever deleted them); the stale-branch sweep detaches HEAD first and
globs via BRANCH_PREFIX; the artifact upload reads env.WORKDIR
instead of a duplicated literal.
The tautological runner-environment gate is repurposed as a
fail-fast docker preflight (a missing daemon otherwise surfaces only
at 'Resolve sandbox image', after npm ci/build), and Install tmux
switches to sudo -n so hosts without passwordless sudo fail with a
clear message instead of dying on a prompt.
The runs-on comments are rewritten to say exactly what they guard:
which events carry the same-repo/write+ clause and which rely on the
live write+ or label gates, why review-address is an author-permission
gate rather than a head-repository gate, and where the pool's docker
availability is proven in-repo (qwen-triage's container jobs on the
same labels).
Contract tests pin all of it: hygiene steps present and ordered
before checkout in every heavy job, the hardened sweep's ordering and
containment in the shared action, the docker preflight and tmux
hardening, the WORKDIR lifecycle, hosted-only short jobs, and generic
next-job slice boundaries so a job inserted after review-address can
no longer hide inside its test slice.
* ci(autofix): inline git-config sanitize step before checkout
The composite sanitize action was invoked as a local action
(`uses: './.github/actions/sanitize-workspace-git-config'`) BEFORE
`actions/checkout` in all three ECS-routed jobs. A local action resolves
from $GITHUB_WORKSPACE, so:
- on any clean workspace it hard-fails with "Can't find 'action.yml'" —
every hosted-fallback run (forks, MAINTAINER_ECS_RUNNER_DISABLED=true,
i.e. the documented rollback path) and any freshly registered ECS
runner — and it cannot self-heal, dying before checkout.
- on a reused workspace it executes the leftover copy, which for
review-address is the previous run's PR branch — pre-checkout execution
of PR-controlled content, the exact vector the step exists to prevent.
Inline the hardened sanitize script as a plain `run:` step in each job
(the qwen-triage 'Clean stale agent state' pattern) and delete the
now-consumer-less composite action. Update the contract test, which had
pinned the broken ordering, to pin the inline run step instead, and add a
generic guard that no local action may precede a checkout.
* ci(autofix): harden PAT handling and pool isolation on the shared pool
Address the Medium findings from the ECS-routing review:
- PAT pushes (Publish PR, Push and report, and the fork dry-run
preflight) now authenticate via a transient credential helper instead of
a token-bearing remote URL. The token enters neither the reused
workspace's .git/config nor the process argv, which is world-readable
via /proc on this shared host; the helper config holds only a
${GITHUB_TOKEN} reference and is swept by the next sanitize pass.
- 'Check runner environment' now asserts the self-hosted runner is an
ecs-qwen pool member by name, so a mis-labelled registration cannot
silently claim a PAT-bearing 300-minute job.
- 'Reset autofix workspace' age-sweeps abandoned /tmp/autofix-* dirs (a
hard runner kill skips the always() teardown and run_id never repeats)
and prunes the reused workspace's .git.
The contract test is updated to pin the tokenless push/fetch URLs, the
credential-helper authentication, and the absence of a token-bearing URL.
* ci(autofix): create autofix workdirs with umask 077 and correct cleanup comments
* ci(autofix): scope PAT credential helper to a one-shot git -c, plus review fixes
Address the latest review round on the ECS-routed heavy jobs.
High — the transient credential helper was not transient: `git config
--local credential.helper` wrote into the reused workspace's .git/config
and the matching unset was skipped on every error path (bash -e abort,
the fork-preflight exit 0, the salvage-loop exit 1s), stranding a helper
that answers any host with the PAT where ci.yml / pr-review jobs could
read it. All three push paths now use a one-shot, host-scoped
`git -c credential."https://github.com".helper=...` (a git_auth wrapper
for the salvage loop), so nothing is ever written to .git/config.
Medium — narrow the sanitize allowlist's `remote.*` to
`remote.<n>.(url|fetch|pushurl)` (uploadpack/receivepack are command
strings the sweep previously kept), and cover all three inlined sanitize
copies in the hardening test with a byte-identical pin (previously one
of three), rewording the stale "composite action" references.
Low — drop the recursive `rm -rf` hooks fallback that could wipe the git
dir itself (warn instead), extend the /tmp sweep glob to the legacy fixed
dir (`autofix*`), and make `git gc` threshold-driven (`--auto --prune=now`).
The contract test pins the git -c / git_auth form, the absence of any
config-written credential helper, and the tokenless URLs throughout.
* ci(autofix): pin gc to the foreground and anchor push auth assertions
Round-2 review follow-ups:
- `git gc --auto --prune=now` detaches by default (gc.autoDetach=true),
and --prune=now is unsafe when other processes write concurrently —
exactly what follows in these 300-minute jobs. Force it synchronous
with `-c gc.autoDetach=false` so the repack still only fires when the
--auto thresholds say so, but never races the checkout, agent commits,
or merge/retry loop that come after it.
- Tighten the two push assertions (dry-run preflight, Publish PR) to
require the host-scoped `git -c credential."https://github.com".helper`
prefix immediately before the push, instead of a bare
`push --no-verify …` match that a `git push` regression would still
satisfy.
---------
Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
|
||
|
|
2c514b50b9
|
fix(autofix): serialize scan-and-pick issue runs in one concurrency group (#8435)
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): serialize scan-and-pick issue runs in one concurrency group The issue-phase concurrency group fell back to github.run_id for every run that route did not force a target — which is every scheduled run and every label-triggered run. Run-unique groups exclude nothing, so two overlapping scans (cron effectively fires every 40-70 minutes while the job may run for up to 180) could both pass the live label recheck during the minutes-long assess call, double-claim the same issue, and burn two multi-hour agent runs on it; the loser then fails its push and posts a withdraw comment. Key forced dispatches per issue, label events on the payload issue, and all scan-and-pick runs (cron or unforced dispatch) on one shared 'scheduled' group. cancel-in-progress stays false so a superseded tick still runs when targets remain. * fix(autofix): make concurrency group pin prettier-stable (#8435) * fix(tests): sync qwen-resolve-workflow timeout pins with repository variables (#8435) * fix(ci): pin issue-phase concurrency group equal to FORCED_ISSUE (#8435) * test(ci): anchor right edge of issue-phase concurrency group pin (#8435) * fix(autofix): keep never-runnable runs out of the issue-phase concurrency group (#8435) * test(autofix): pin issue-autofix concurrency gate to the job if predicate (#8435) * fix(autofix): exclude dry runs from the issue-phase concurrency groups (#8435) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(autofix): pin the Claim/Publish dry-run step gates (#8435) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: verify <verify@local> Co-authored-by: 易良 <1204183885@qq.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
8fd0162c68
|
fix(release): normalize Live Host signing identity (#8579) | ||
|
|
89b3d5ea8e
|
fix(autofix): ship core dist in the review CLI bundle (#8612)
* fix(autofix): ship core dist in the review CLI bundle The review fan-out (#8548) dropped the per-leg build: legs restore the shared bundle's repo-root dist/ and rely on the verify gate's full build for branch verification. But the gate's settings-schema and i18n checks run BEFORE any build, on every path including no-action, and their tsx-transpiled cli sources import '@qwen-code/qwen-code-core', which resolves through the workspace symlink to packages/core/dist/index.js. With no build on the leg, the generator crashes with ERR_MODULE_NOT_FOUND and the gate misreports a deterministic "settings schema is stale" rejection (run 31031063525 on PR 8600), then burns an 18-minute repair agent round on an environment problem no agent can fix. Ship packages/core/dist (+~8.5MB gzipped) alongside the root dist/ in the fan-out artifact and assert its entry point on restore. This restores exactly the pre-fan-out state: legs used to build the trusted base themselves before the branch checkout, so the gate always ran against base-built core dist. The workflow contract tests pin the new tar command and the restore-side assertion. * fix(autofix): rebuild branch-touched core dist before the schema gate Review feedback on the core-dist bundle fix: - Rebuild packages/core from branch sources in the review verify gate when the branch diff touches core's sources, so the pre-build settings-schema check never compares the branch's committed schema against a base-built dist (changed runtime constants) or crashes the generator (changed exports). Lives in the shared gate script so both the initial and the repair gate are covered. - Narrow the bundle/restore comments and their test mirror to the settings-schema generator: the i18n check resolves core to sources via the packages/cli tsconfig paths map and needs no dist (verified empirically). - Anchor the tar contract pin at end-of-line so additive path drift fails the suite instead of passing on a substring match. |
||
|
|
3235faf418
|
ci: add hk ECS runners to update matrix (#8599)
Some checks failed
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 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
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
npm cache producer / Save npm cache (push) Has been cancelled
Update ECS Runner Qwen / Update Qwen on ecs-update-64c (push) Has been cancelled
Update ECS Runner Qwen / Update Qwen on ecs-update-hk-1 (push) Has been cancelled
Update ECS Runner Qwen / Update Qwen on ecs-update-hk-2 (push) Has been cancelled
Update ECS Runner Qwen / Update Qwen on ecs-update-sg (push) Has been cancelled
|
||
|
|
a123d0030a
|
ci(review): prepare evidence-image tooling for GitHub-triggered reviews (#8454)
* ci(review): prepare evidence-image tooling for GitHub-triggered reviews Reviews triggered on GitHub cannot attach images today for three reasons; this wires the two that live in the workflow: - Install tmux and freeze (pinned, checksum-verified) before the review runs. Both are optional by contract — the evidence ladder degrades honestly without them (png -> ans-only -> refused, recorded in the capture manifest) — so the step never fails the review; it only decides which rung the runner can reach. tmux mirrors the tolerant install qwen-autofix.yml already uses; freeze falls back to ~/.local/bin when passwordless sudo is absent. - Pass QWEN_REVIEW_ASSETS_REPO from a repository variable to the review step. Publishing stays OPT-IN by design: with the variable unset the env is empty and publish-assets refuses (parseAssetsRepo trims and rejects empty), so nothing changes until a maintainer sets the variable. When set, evidence images land on commit-pinned pr-assets/<pr>-review branches — already covered by the visuals cleanup workflow — pushed with the same CI_BOT_PAT the step uses. The third reason is release lag: the capture producer (capture-tui, #8388) has to merge and ship in a release before rendering claims can generate images on CI at all. This change is inert until then. * fix(ci): capture-tools step review fixes — enforced tolerance, version pin, cached fallback R1-1: the never-fails contract is now enforced twice — continue-on-error at the YAML level (the belt) and set +e with a trailing exit 0 inside (the suspenders); under the runner's default bash -e several statements (mktemp, install, sudo install with an empty path) could previously abort the step and fail the review the comment promised never to fail. R1-6: probe the VERSION, not just the binary — on a persistent self-hosted runner an installed freeze made any FREEZE_VERSION/SHA bump a silent no-op; the pin now forces a refresh when the cached binary does not match. Cached-fallback fix: put ~/.local/bin on PATH (and GITHUB_PATH) before the probe — a sudo-less runner otherwise re-downloads the tarball on every review run forever. R1-3: the step comment says capture-tui is UPCOMING (#8388, not in the released CLI) and names qwen review drive as today's tmux consumer, so the step cannot be mistaken for stale dead weight and deleted from under the follow-up. R1-4: the retention comment scopes the cleanup-workflow claim to the same-repository designation; a fork or scratch destination manages its own retention (docs updated to match, plus a note documenting the repository VARIABLE a maintainer sets to enable publishing). R1-5: the step's real bash now runs in the workflow behavioural harness under bash -e with stubbed sudo/apt/curl/sha256sum/tar/uname: worst-runner and checksum-reject scenarios exit 0 installing nothing, the no-sudo happy path pins the ~/.local/bin + GITHUB_PATH pairing, and the version-pin probe is pinned from both sides (wrong version re-downloads, matching version skips). Real freeze/sudo on a developer machine are shadowed so the tests are deterministic and can never install to /usr/local/bin. Nit: both sudo guards now check sudo -n true. * fix(ci): capture-tools step review fixes — step-owned tool dir, anchored probe, honest failures Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(ci): capture-tools test harness — shadow tmux, don't blank its PATH dir The harness dropped every host PATH directory that ships a tmux so the step's apt branch would depend on the scenario, not on the machine hosting the suite. On GitHub-hosted ubuntu runners tmux lives in /usr/bin, so the filter blanked /usr/bin wholesale — bash, grep, mkdir, and tar included — and execFileSync('bash') died of ENOENT: all seven capture-tools tests failed in the Test (ubuntu-latest Node 22.x) job while passing on tmux-less dev machines. Replace the directory-level drop with an entry-level shadow: each tmux-bearing directory is mirrored (symlinks) into a scratch dir minus the tmux entry, in place, preserving PATH order and the empty-entry stripping the old filter did. Hosts without tmux take the map through unchanged, and Windows (no tmux in its PATH, no symlink branch) keeps its current behavior exactly. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(ci): capture-tools test review fixes — faithful stubs, env shape pins, missing-branch scenarios Round-3 review findings: the harness executed several of the step's branches but asserted nothing about them, so probe-verified mutants (dropped tmux guard, deleted warning/degradation messages, malformed or missing FREEZE_VERSION/FREEZE_SHA256, wrong hash variable, dropped URL `v` prefix or curl `-L`, severed tarball paths, broken regex boundary, leaked mktemp dir) all shipped green. - Make the curl/sha256sum/tar stubs model their real contracts: exact pinned URL, pinned checksum over a file curl actually wrote, existing -xzf operand - Pin FREEZE_VERSION/FREEZE_SHA256 shape in captureToolsSource - Pin the full curl flag set and the three-site tarball path agreement - Assert the stale-renderer warning (fires on degraded re-download, silent on the happy path) and the tmux-unavailable message - Pin TMPDIR and assert the mktemp cleanup leaves it empty - Add the two missing scenarios: tmux-present skips apt, cached version extending the pin with a leading digit re-downloads Verified by 13 mutation probes: every named mutant now turns the suite red (13/13 killed), baseline 34/34 green. * fix(ci): capture-tools step review fixes — hash-verified cache, per-run PATH promotion * fix(ci): capture-tools step review fixes — verified-bytes-only installs, step timeout Review findings on the capture-tools step: - Drop the PATH-trust branch: a freeze already on PATH was accepted on its own --version and executed to probe it — exactly the self-report the FREEZE_BIN_SHA256 comment declares attacker-controllable, from dirs writable between jobs on both runner classes. The checksummed download always runs now; the cache makes it free after the first run. - Guard $tools_bin in the download branch: with mktemp failing, the unguarded install resolved to /freeze — harmless unprivileged, but a root-in-container self-hosted runner writes it and reports success with nothing on PATH. - Copy-then-verify the cache: install into the fresh per-run dir FIRST, verify THOSE bytes, delete both copies on mismatch — the verified bytes are the bytes later steps execute, closing the check-then-copy race for free. This makes the separate pre-verify block redundant; it is deleted. - Add timeout-minutes: 5 — continue-on-error bounds failure, not duration, and a stalled `sudo apt-get update` mirror had no other bound under the 300-minute job cap. - Report block: say the resolved freeze is likely broken when its --version produces nothing, instead of echoing a blank line and calling it stale; the mismatch wording is direction-neutral now. Tests: replace the PATH-trust scenario with a planted-PATH one (marker outside the scenario dir proves the plant never executes), add the mktemp-failure scenario (the install stub succeeds like root would, so the unguarded mutant is caught) and the promoted-dir 0700 assertion; re-anchor the two digit-boundary tests on the report's warning. 41/41 green; both fix mutants verified killed. * fix(ci): capture-tools review fixes — stale-dir cleanup, pinned guards Address round-5 review: - R5-1 (Critical): the per-run qwen-review-tools.* dir under RUNNER_TEMP was never removed; RUNNER_TEMP survives across jobs on the shared pool, so every review run accumulated one dir + one Go binary, unbounded. 'Clean stale agent state' now removes stale dirs before the install step creates the current run's dir, matching the qwen-triage.yml convention. The harness comment claiming the dirs were runner-cleaned is corrected. - R5-6: the cache re-verification rejection branch now logs why it deletes the cached binary instead of degrading silently. - R5-7: bump-checklist note beside the freeze pins — the harness stubs key on the same env values, so a transposed hash pair must be caught against the real release artifacts at bump time. - R5-2/R5-3/R5-4/R5-5: four unpinned step properties now pinned (the if: guard, the sudo -n probe flag, install-after-context ordering, and the cache branch's tools_bin guard via a new mktemp-fails scenario); six mutation probes confirm each pin kills its mutant. * fix(ci): capture-tools review fixes — curl budget, swept scratch dir, wiring pins * fix(ci): capture-tools review fixes — harness mutation pins, pin-pair self-check Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(ci): capture-tools review fixes — shadow-farm cleanup, backoff budget term * fix(ci): capture-tools review fixes — report probes only installed freeze, age-gated sweep --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
c73b5ed887
|
ci: run Windows merge queue tests on ECS (#8386)
* ci: run Windows merge queue tests on ECS
* test(channels): skip POSIX mode assertion on Windows
* ci: expose Git Bash on Windows ECS runner
* ci: scope Windows ECS tuning to self-hosted and restore full test:ci
Review feedback on the Windows ECS routing: dropping test:scripts removed the only Windows execution of 9 Windows-only install-script tests, and the job-wide PowerShell default plus narrowed test command changed the kill-switch fallback away from the known-good hosted configuration.
Restore the full npm run test:ci on both paths (bash is available: pre-installed on hosted runners, exposed via the Git Bash PATH entry on ECS) and gate every ECS-specific adjustment on runner.environment: the PowerShell setup step (now also skip_ci-guarded), TEMP/TMP/LC_ALL env writes, and the Linux-style Node setup split that fails with an actionable error naming MAINTAINER_ECS_RUNNER_DISABLED. The windows-2022 fallback is byte-for-byte the pre-ECS job again.
* test: make Windows CI suites platform-aware
* ci: add stale-checkout guard to Windows ECS test job
* test(core): compare canonical directory identity
* ci: add fork guard and review follow-ups to Windows ECS job
* test(core): exercise real directory identity change
* test(core): wait for killed lease process exit
* test(scripts): avoid cmd echo trailing spaces
* test(scripts): use unambiguous cmd echo syntax
* test(cli): avoid sidecar I/O in truncation test
* test: fix Windows script-suite gaps and unify platform gating
- Fix missed trailing-space cmd stub in package-scripts.test.js so the
'runs prepare steps in order' assertion passes on Windows.
- Add qwen-pr-review-workflow.test.js and pr-self-report-label.test.js to
the win32 exclude list (both test Linux-only workflows and are not
portable to Windows).
- Replace local itPosix/describeOnNonWindows consts with vitest's built-in
it.skipIf/it.runIf/describe.skipIf, matching the codebase idiom.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* test(scripts): restore Windows workflow coverage
* test(scripts): re-exclude Windows-incompatible workflow tests on win32
Re-add pr-self-report-label.test.js and qwen-pr-review-workflow.test.js to
the win32 exclude list. Both fail on a Windows runner for reasons the code
still carries: qwen-pr-review-workflow.test.js calls execFileSync('mkdir'),
which has no executable to resolve there, and pr-self-report-label.test.js
joins PATH with ':', corrupting the ';'-separated Windows PATH so its gh
stub never resolves. Excluding them restores a green Windows gate; Linux CI
remains their authoritative coverage. Document the criterion inline.
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* ci: extract checkout-head guard into composite action, pin Windows gate (#8386)
Address review round 2: move the stale-checkout guard shared by the four CI gates into .github/actions/verify-checkout-head so the copies cannot drift, pin the Windows gate kill-switch routing and guard wiring in the script tests, re-enable lint.test.js on Windows via separator normalization and a lazy linter setup in scripts/lint.js, unify the platform skips on it.skipIf(process.platform === 'win32'), and document the queued-run behavior of the ECS kill switch.
* ci: fail fast in Windows gate environment setup (#8386)
* ci: dedupe self-hosted runner steps into actions, pin gate mutations (#8386)
* fix(ci): checkout before repository-local actions in Windows gates (#8386)
* fix(ci): configure Windows runner before bash guard
* test(ci): pin remaining shared-action wiring in script tests (#8386)
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
* fix(ci): skip zip-dependent packaging tests when zip is missing (#8386)
* fix(ci): validate full Windows smoke path
* fix(ci): match Windows smoke shell to gate and drop dead runs-on guard (#8386)
* fix(ci): make SIGTERM escalation test Windows-aware and tighten pins (#8386)
The CDP acceptance test asserted a POSIX-only SIGKILL escalation, which
fails deterministically on Windows where kill('SIGTERM') terminates the
child directly — blocking the Windows merge-queue gate. Assert the
platform-appropriate signal instead.
Also address review suggestions: probe `unzip` alongside `zip`, pin the
integration_cli guard's missing step-level `if:`, stop getWorkflowStep
at unnamed steps, pin install-script.test.js out of the win32 excludes,
add the stale-checkout guard to windows-runner-smoke.yml, pin the
Node preflight warning branch and the guard reject path contiguously,
and extend the smoke shell-parity loop to the npm cache step.
* docs(ci): clarify Windows runner trust boundary
---------
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-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: qwen-code-ci-bot <qwen-code-ci@service.alibaba.com>
|
||
|
|
93cd019088
|
fix(release): reuse configured Apple signing secrets (#8574) | ||
|
|
a5c637b749
|
feat(web-shell): add native Live Voice (#7859)
* feat(web-shell): add native Live Voice * fix(web-shell): address review feedback for Live Voice PR (#7859) - Quote all strings in electron-builder.yml to fix yamllint CI failure - Gate discovery publish on liveVoiceEnabledAtBoot to avoid writing bearer token to disk when Live Voice is disabled (M1) - Add child identity guard to CommandMonitor stdout/stderr handlers to prevent stale helper output from corrupting the new buffer (M4) - Add exponential backoff to sent-completion delivery retry (M3) - Skip broadcastState when setCallState/setTranscript value is unchanged to reduce per-audio-delta overhead (H1) - Document sent-mode completion notification in module docstring (H2) - Remove dead protocol/nonce aliases from readDiscoveryFile - Fix single instance lock fall-through with process.exit(0) * fix(cli): register realtime_voice in docs contract and env guard (#7859) * fix(web-shell): address review feedback for Live Voice PR (#7859) * fix(cli): discard orphaned isolated dir when parent restore fails (#7859) * fix(web-shell): address review feedback for Live Voice PR (#7859) * fix(serve): harden live turn recovery * fix(desktop): restore Live Host native build * fix(live): align native host and session isolation * fix(acp): preserve live worker continuation lineage * fix(live): classify provider close reasons * fix(serve): discard unused recovered conversation dirs * fix(live): isolate authorized realtime responses * fix(live): preserve realtime response authority * feat(web-shell): complete Live Voice onboarding * fix(live): persist realtime-owned dialogue * fix(live): preserve final speech while stopping * Revert "fix(web-shell): address review feedback for Live Voice PR (#7859)" This reverts commit 7110bec6b034c702bca6e28e35b93c7f70e729cd. * Revert "fix(cli): discard orphaned isolated dir when parent restore fails (#7859)" This reverts commit 85165f1b2ddfaa311b8be91acdd76a6f388f6204. * Revert "fix(web-shell): address review feedback for Live Voice PR (#7859)" This reverts commit 9199fa633e102bb8f24e4b216d322be4323eb3fc. * Revert "fix(cli): register realtime_voice in docs contract and env guard (#7859)" This reverts commit 6b6b1718352ef01a98a73976b5c7c4433fd14c35. * Revert "fix(web-shell): address review feedback for Live Voice PR (#7859)" This reverts commit e083779105199d26de3afd8ad00719a08efe3099. * revert(live): remove remaining takeover behavior * revert(live): restore pre-rollback implementation * test(cli): align Live diagnostics env guard * test(release): cover Live Host publication * fix(ci): re-sign Live Host package before verification * fix(serve): scope sent completion notifications to Live * fix(web-shell): preserve live setup errors * fix(live): align realtime backend speech lifecycle * ci(live): publish Live Host independently * test(cli): mock Live speech bridge handler * test(release): align Live Host workflow contract * fix(live): address release and lifecycle review findings * fix(live): release completed call tracking --------- Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com> Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> |
||
|
|
67d128715e
|
chore(cua-driver): sync upstream v0.17.0 (#8564) | ||
|
|
6d4d9b5238
|
perf(review): retire dry chunks and pipeline verification in the reverse audit (#8498)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
* feat(review): retire dry chunks from reverse-audit rounds and pipeline verification Rebuild of the retirement/pipelining feature as one commit on top of the merged reverse-audit budget gate (#8468). - retirement.ts: per-chunk scheduler over the CLI's own prompt records and the harness transcripts — a chunk whose two most recent audits are substantive dry receipts is cold-checked on the even rounds instead of audited on every one, and a cold check that yields returns it to every-round auditing. The certifying match is counted records per transcript (one launch matching several records certifies none), dry receipts are read structurally with the zh forms beside the English ones, and a filed finding requires the File+Severity pair so an echoed quotation cannot pin a chunk hot. Everything fails toward auditing. - agent-prompt --all-chunks: requireAuditableChunks, then the schedule (round >= 3, fail-open to all-due on any error), then CONVERGED exit 5 (nothing built, no stamp, no marker), then the budget gate (exit 4 + marker), then the build. The admission stamp keeps the #8468 ordering and lands only after the build succeeds: a cold-check-only round that builds still stamps, a converged round never does, and a build that throws leaves no stamp. - agent-prompt --chunk: a round holding an admission stamp is repaired without gates or scheduling; an unadmitted round answers to the same sequence as --all-chunks (convergence, then budget), and its first chunk build is the round's admission — stamped after the build. - prompt-record: optional sinceMs fence on readRecordedPrompts (history readers only; coverage's obligation reads stay unfenced), plus the flattenPrompt/deliveredVerbatim split so the scheduler flattens each launch once instead of once per (record, transcript) pair. - deadline: doc-comments rewritten for the pipelined cadence — the admission-to-admission measure no longer contains a verification pass, so the tail reserve is the terminal round's only cover (replacing the 'deliberate margin' overlap rationale), and the workflow's reserve cap is cross-referenced. runEpochMs fencing, the bilingual budget-stop marker and the stamp semantics are unchanged from #8468. - SKILL Step 5: builder-owned 3B scheduling, the CONVERGED exit-5 termination rule, verification launched alongside the next round's auditors, and the cumulative reported list with '— [unverified]' tagging (added at the admitting merge, cleared or removed after the verdict; anything still tagged is excluded from Step 6). Superseded pieces of the parallel branch were dropped in favour of the #8468 form now on main: the planMtimeMs-equality fence (runEpochMs stays), the budget-scaled round-1 estimate, stamping inside the admission helper, and the branch's variants of the budget-gate tests. * test(review): split the tool-call guards in the retirement classifier tests Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(review): harden the retirement classifier and pin its gate orderings (#8498) Review-round fixes for the per-chunk retirement feature: - retirement.ts: admit the full-width colon (U+FF1A) in zh dry receipts, which the old [::] class silently refused; refuse the brief's own example receipt and prose artifacts (a stray backtick, the conjunction "and/or") in the substance check; require the transcript's diff reads to overlap the chunk's baked territory before a dry receipt counts; stop counting a quoted cumulative-list entry — a File+Severity block whose file line appears verbatim in the agent's own launch prompt — as a filed finding. Every change fails toward auditing. - agent-briefs.ts: export the reverse-audit example receipt (REVERSE_AUDIT_EXAMPLE_RECEIPT) and interpolate it in the brief, so the brief and the classifier's parrot refusal cannot drift. - Tests: rewrite the transcript-fence test, which a future-dated plan made vacuous, and add probe-verified pins for every fix plus the previously unpinned guards — converged-before-budget under deadline on the --chunk path, the --chunk transcripts-unavailable degrade, the stamp-keyed repair exemption (records without a stamp stay refused), budget-stop marker absence on the admission side, uncertified cold-check recovery, the history-less chunk guard, and the same-round multi-record merge in both digest orders. - SKILL.md: schedule the Step 5 findings merge unconditionally — verdicts land on dry rounds too — and cap a would-be Approve at COMMENT when a reverse-audit entry's verifier never ruled on it. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(review): bind the retirement territory to the diff read and reprice the budget gate (#8498) * fix(review): machine-check the unverified-tag backstop and reprice the tail reserve (#8498) --------- 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-Coder <qwen-coder@alibabacloud.com> |
||
|
|
da37110e60
|
perf(autofix): build the review CLI bundle once per scan and fan it out to legs (#8548)
* perf(autofix): build the review CLI bundle once per scan and fan it out to legs Each review-address leg repeated the same trusted-base build: measured 3.5-5 minutes of npm ci + build + bundle per leg (~25 runner-minutes on one 6-leg scan) before the agent could start. A build-cli job now compiles the bundle once per scan, uploads the repo-root dist/ as an artifact, and the legs download it; their checkout is pinned to the compiled SHA so a mid-run base push can never pair a leg's bundle with different sources. The legs keep npm ci (the agent and the verify gate still need node_modules against the PR branch), and the issue phase is untouched — it runs only when no review targets exist, so gating the build on do_issue too would rebuild on every quiet scheduled tick. * fix(autofix): validate fan-out bundle SHA and pin shared CLI recipe contracts (#8548) 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-ci-bot <qwen-code-ci-bot@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
e34780e24d
|
fix(ci): clean review worktrees after cancellation (#8474)
* fix(ci): clean review worktrees after cancellation * fix(ci): remove orphaned review worktree directories * fix(tests): sync qwen-resolve-workflow expectations with externalized review timeouts (#8474) * fix(ci): pin review worktree cleanup patterns to paths.ts (#8474) * fix(ci): harden review cleanup sweeps and cover integration_cli (#8474) * fix(ci): extend review cleanup sweep to web_shell_e2e_smoke (#8474) * fix(ci): harden review cleanup git calls * fix(ci): tighten review cleanup comments and test guards (#8474) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(ci): pin review cleanup recipe copies byte-identical (#8474) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(ci): guard review worktree removal and pin cleanup invariants (#8474) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
21ad3b464a
|
fix(desktop): codesign ripgrep and node binaries before tauri build (#8518)
* fix(desktop): codesign ripgrep and node binaries before tauri build macOS notarization rejects the app bundle because Tauri only signs the main binary, not the embedded ripgrep and Node.js runtime binaries under Contents/Resources/runtime/qwen-code/. Add a pre-build codesign step that signs all native macOS executables in the bundled runtime with the Developer ID identity, hardened runtime, and the existing entitlements. * fix(desktop): allow Windows build without signing certificate The Tauri release workflow threw when WINDOWS_CERTIFICATE was missing, blocking the entire release (including macOS). Mirror the old Electron workflow behavior: warn and continue unsigned when no cert is configured. Also add fallback to legacy WIN_CSC_LINK/WIN_CSC_KEY_PASSWORD secrets so existing Electron-era credentials still work if present. * fix(desktop): allow unsigned Windows artifacts in verify step The 'Verify Windows signature' step threw on any non-Valid status, including NotSigned. With no Windows code signing certificate configured, this blocked the Windows build job, which in turn blocked the publish job (needs: [prepare, build]). Allow NotSigned with a warning instead of throwing, matching the fallback behavior of the 'Import Windows certificate' step. A genuinely invalid signature (HashMismatch, etc.) still throws. * fix(desktop): narrow find scope and deduplicate pfx import - Scope ripgrep codesign find to *-darwin/* paths so Linux ELF binaries (built in the same matrix) aren't targeted. - Unify the two pfx-import branches into a single code path to eliminate duplicated write/import/configure logic. * fix(desktop): guard optional Windows signing config * fix(desktop): harden vendor signing workflow |
||
|
|
d2c7cb1f15
|
fix(autofix): normalize paginated fetches to one flat array per file (#8438)
* fix(autofix): normalize paginated fetches to one flat array per file gh api --paginate emits one JSON array PER PAGE, so any PR past 100 comments/reviews/events produces a multi-document stream. The workflow already slurps correctly in a few readers (jq -rs add, --slurpfile + add), but more than a dozen plain-jq consumers of the WORKDIR files mis-aggregate on a multi-doc input: - MARKERS/REARM_AT/RED_HEAD/REARM_KEY in the scan and their LIVE_* mirrors in prepare emit one result per page; ROUND then becomes a multi-line string, [[ -ge ]] arithmetic fails, and the round cap silently stops holding — on exactly the PRs (takeover, 100-round cap, one report comment per round) that reach page two first. - CAP_NOTICED / BASE_UPDATE_RECENT / LAST_REJECTION / PRIOR_TIMEOUTS / the milestone census and the report-step consecutive-failure census all degrade the same way. - NEWEST and LIVE_NEW bind rv/rc/ic/checks POSITIONALLY (.[0]..[3]); a two-page rv.json shifts rc/ic into the wrong slots and later feedback is silently lost. Fix at the fetch sites: every --paginate that lands in a WORKDIR json file (and the report step's COMMENTS_JSON fallback) now pipes through jq -s 'add // []', so each file holds ONE flat array. Existing slurp-style readers are unaffected — add is idempotent over a single array — and every plain consumer becomes correct past 100 items with no program changes. Failure semantics are preserved: the workflow-level bash default gives -eo pipefail, so a failed gh still fails the pipeline exactly where it failed the bare redirect before, and the pr-events/COMMENTS_JSON fallbacks keep their '[]' paths. The check-runs/annotations/status-comment reads stay raw on purpose: they aggregate per-page via --jq + slurp, line-streams, or .[][] and were already pagination-safe. Tests: a behavioral case runs the real MARKERS→ROUND pipeline and the positional NEWEST program against two-page fixtures through the normalizer, with negative controls demonstrating the pre-fix corruption (two MARKERS lines; the page-2 review timestamp lost to slot shift). Shape assertions pin all nine normalized fetch sites and ban raw --paginate file redirects. * fix(autofix): pin total --paginate occurrence count in tripwire test (#8438) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(autofix): correct gh --paginate merge model in pagination comments (#8438) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(autofix): make engage-ack ic re-fetch atomic on failure (#8438) * test(autofix): pin atomic engage-ack re-fetch and empty-input normalization (#8438) --------- Co-authored-by: verify <verify@local> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
ac67de2e46
|
fix(review): stop the reverse-audit loop while there is still time to report (#8468)
* fix(review): stop the reverse-audit loop while there is still time to report Measured on CI run #8368 (+1699 lines): the iterative reverse audit ran to its 5-round cap, each round a per-chunk fan-out whose findings then went back through verification, and the loop consumed 3.5 of the job's 4 budgeted hours. The outer GNU-timeout kill arrived while round 5's findings were still being verified. The review died holding every confirmed finding it had; nothing reached the pull request. The loop's rounds are driven by the orchestrator, but every round begins at the same place: agent-prompt building the round's prompts. So the builder becomes the loop's clock. When the environment carries a review deadline (QWEN_REVIEW_DEADLINE_EPOCH, exported per attempt by the review workflow) and the remaining time is inside the reserve kept for the last verification, compose-review and submission (default 60 minutes, QWEN_REVIEW_DEADLINE_RESERVE_SECONDS to override), a reverse-audit round is refused: a BUDGET line on stderr, exit code 4, no prompt built and no record written. The message carries the exact unreviewedDimensions entry to file, so the disclosure that caps the verdict is the CLI's text, and Step 6 proceeds with the findings already confirmed. Local runs have no deadline and are untouched. A malformed deadline fails open — the outer kill still bounds the run, and a broken variable must degrade to today's behaviour rather than wedge every budgeted review at round 1. The verifier is deliberately not gated: the reserve exists so it can run. * fixup: scale the deadline reserve to the externally-chosen budget The budget is not this workflow's to assume: it arrives from a repository variable, a workflow input, or a /review --timeout=N comment. A fixed 60-minute reserve would consume most of a 70-minute budget and refuse the audit loop outright on a 30-minute one. The workflow now passes a reserve of a quarter of the attempt, floored at 10 minutes and capped at 60; the CLI constant remains only the fallback for a caller that sets a deadline without a reserve. * review feedback: admit the round only if IT fits, and cap deterministically Three findings from review, all taken: 1. The gate budgeted for the tail but not for the round it admits — the terminal round is by construction the one that starts closest to the boundary, so the killed-mid-verification failure survived one round wide. The gate now requires remaining >= round + reserve, where the round's cost is the previous round's, measured admission-to-admission from a stamp the builder writes (one per round; a same-round rebuild is not a round), falling back to a 30-minute constant for round 1, which starts with the most headroom. 2. The refusal was deterministic; the disclosure that caps the verdict was prose the orchestrator had to carry. The builder now records a budget-stop marker beside the prompt records and compose-review synthesizes the unreviewedDimensions entry from it — deduped against a relayed copy — so a run that drops the sentence still cannot approve past a truncated audit. 3. Exit code 4 is documented in the command's describe. Also restores the Step 5 bullet the previous commit's edit displaced (new findings merge into the cumulative list before the next round). * review feedback: pin the budget gate's all-chunks refusal and ordering Cover the two behaviours the review noted were only asserted on the bare --findings form: an exhausted budget refuses the loop's real --all-chunks round before ANY of the per-chunk records is written, and a malformed call (--round 0) still gets its validation error first — exit 4 is for a well-formed round the budget refuses, never a replacement error. Also name what the code already does: reserve=0 is the deliberate escape hatch (the gate shrinks to the round estimate alone), and the workflow's 3600s cap mirrors DEFAULT_RESERVE_SECONDS. * docs(review): describe the soft-deadline env vars for time-budgeted runs The review noted the two new variables appeared in no user-facing doc; the reserve in particular is an operator-facing knob. State what each does, the fail-open posture, and how the refusal surfaces in the verdict. * fix(cli): align budget-stop disclosure with the gate's refusal (#8468) A round-1 budget refusal left no reverse-audit records, so the Step 4/5 floor reported the deliberate stop as a rogue/unlaunched audit with a rebuild FIX the same gate deterministically rejects; the refusal's own disclosure was swallowed by the caller-echo dedup. The floor now stands down when the budget-stop marker exists, and compose-review renders the disclosure structurally, bilingually, from the marker. Also: `--role reverse-audit` requires `--round <k>` (an unlabeled admission stamps an entry no estimate can attribute), the budget gate runs after the plan/findings reads (a broken plan or unreadable findings deserves its own error, and nothing is stamped ahead of a buildable call), and the gate's admission boundary, measured-cost behaviour, and the workflow env contract are pinned by tests. * review: a budget stop excuses only the round it refused The budget-stop suppression keyed on the marker's existence alone, so every reverse-audit gap shape went silent once any round was refused — including the shapes that describe rounds which RAN before the budget hit. A hand-written round-1 launch is exactly as undelivered when round 3 later hits the budget, and suppressing its disclosure let 'stopped before round 3' imply the rounds that did run were faithful. Exactly one shape is by design under a marker: not-built — the refusal writes no record, so an audit with no records IS the audit the gate stopped, and its FIX (rebuild the round) would be refused by the same gate. The suppression now names that shape and no other; a rewritten, unlaunched or brief-unread round keeps its disclosure and its repair. The new test pins the operative halves: the verdict stays capped, the marker's disclosure posts, and the operator channel carries the rewritten round's exact repair. (The posted body collapses same-subject disclosures — both say 'reverse audit' — so the author sees the stop; repairs are acted on from stderr, where the rewritten fix rides.) * fix(review): fence budget state per run, and let gate errors beat budget stops Address the round-2 review threads on the reverse-audit budget gate: - Fence budget-rounds.json and budget-stop.json by the plan's own mtime. Every run rewrites the plan at its Step 1 capture, so records older than the plan belong to a previous run of the same PR: a run killed before cleanup no longer prices the next run's rounds off stale stamps (an hours-old stamp read as an hours-long round refused round 1 of a fresh budget) and no longer caps a later run's verdict on a stop that did not happen in it (R2-1, R2-2). - Refuse a structurally unbuildable plan (no chunks[], duplicate or non-integer ids) with its own error ahead of the budget gate, so the same corruption gets the same diagnosis whatever the clock says, and no budget-stop marker is written over a corrupt plan (R2-5). - Stamp a round admitted only after its build succeeds: a build that throws leaves no stamp, so the next round's cost is never measured from a build that produced nothing and floored to 600s (R2-6). - Keep the budget entry's 'reverse audit' subject out of the caller-echo prefix filter: other reverse-audit scopes the orchestrator disclosed (a twice-whiffed chunk from the rounds that DID run) are no longer silently dropped in the marker's shadow; the marker's own relays stay deduped by the phrase splice (R2-7). - Render --round unbracketed in the reverse-audit rebuild fix — the CLI refuses a round-less reverse-audit call, so the paste-and-run repair must not present the flag as optional (R2-14). - Document the deliberate one-verification overlap between the measured round estimate and the tail reserve, at both definitions (R2-13). - Test hardening, each assertion mutation-probed to fail its named mutant: a reshaped relay only the marker-phrase splice dedups (R2-8); the stamp's round label and the verifier's no-stamp invariant (R2-9); whole-line, unit-arithmetic and reserve-cap pins on the CI wiring contract (R2-10); the first-wins stamp survivor (R2-11); the reserve=0 escape hatch (R2-12). --------- Co-authored-by: verify <verify@local> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
06cc41ee3f
|
ci: route trusted-author fork PRs and no-checkout jobs to the ECS pool (#8502)
Some checks failed
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 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
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
npm cache producer / Save npm cache (push) Has been cancelled
* ci: route trusted-author fork PRs and no-checkout jobs to the ECS pool Fork PRs whose author has write access (OWNER/MEMBER/COLLABORATOR association) now run Linux CI on the self-hosted ECS pool instead of the saturated GitHub-hosted quota, and bot workflows that check out no code move to ECS unconditionally. Everything stays gated on the MAINTAINER_ECS_RUNNER_DISABLED kill-switch. * ci: address review — real write-permission routing, watchdog independence, timeouts Route the triage agent on the collaborator-permission API result computed by authorize instead of the coarse author_association, which admits org members and read-only collaborators; the two permission-gate jobs revert to the same-repo guard. Keep the fleet watchdog and the CI-failure reporter hosted so they stay independent of the pool they watch. Add missing timeouts, wipe serve-ab's reused workspace, and pin the routing logic with drift and negative-case tests. --------- Co-authored-by: 易良 <1204183885@qq.com> |
||
|
|
20b3087aca
|
feat(browser-ext): add alpha readiness diagnostics (#6739)
* feat(browser-ext): add alpha readiness diagnostics * test(browser-ext): automate readiness verification * fix(browser-ext): support current devtools adapter * test(browser-ext): verify restored page after reconnect * fix(browser-ext): harden release and acceptance checks * test(browser-ext): cover onboarding transitions * fix(browser-ext): harden alpha diagnostics * test(cli): sync serve capabilities baseline * feat(browser-ext): add alpha readiness diagnostics * test(browser-ext): automate readiness verification * fix(browser-ext): support current devtools adapter * test(browser-ext): verify restored page after reconnect * fix(browser-ext): harden release and acceptance checks * test(browser-ext): cover onboarding transitions * fix(browser-ext): finalize Chrome Web Store package * fix(browser-ext): harden CDP diagnostics per review feedback (#6739) * fix(browser-ext): harden CDP diagnostics per review feedback (#6739) Distinguish the ACP child's idle placeholder (initialized: false, discoveryState: 'not_started') from a genuinely empty server list so the panel no longer shows a false "adapter is not connected" warning before the first session or after the child is reaped. Compare the tunnel endpoint's host+port against the daemon baseUrl to detect cross-daemon shadowing (a chrome-devtools entry pointing at a different daemon's /cdp was previously reported as connected). Guard package-extension and symlink tests with skipIf(process.platform === 'win32') so the Windows merge-queue gate does not fail on missing zip.exe or privilege-dependent symlinkSync. Also: destructure QwenCapabilityStatus lazily inside probeState so a missing capability-status.js no longer throws before the welcome screen renders; add the missing license header to manifest-version.js; replace the leftover #welcome height:100vh with flex sizing; add cross-reference comments for the shared /cdp path pattern. Note: probeJson intentionally drops the .catch(() => ({})) fallback so a 200 with a non-JSON body reads as unreachable; this also makes /health stricter than before. * fix(browser-ext): resolve CDP diagnostics review findings (#6739) * fix(browser-ext): mirror nightly build number in manifest test oracle (#6739) * fix(browser-ext): address alpha diagnostics review feedback (#6739) - declare the semver dependency used by manifest-version.js so an isolated workspace install no longer relies on root hoisting - make artifact-scan skip the root CLI bundle metafile with a warning when it is absent (it only exists after `cross-env DEV=true npm run bundle`), keeping the extension metafile required, so package-level test:release no longer fails - throttle the side panel /workspace/mcp probe to every 5th tick and reuse the cached snapshot in between, avoiding a cross-process RPC on every 2s poll - document the per-session CDP event fan-out and pin single-path event counts; note that Target.getDevToolsTarget is deliberately unsupported - guard the nightly build-number git lookup and the zip end handler - disclose the daemon-to-model-provider page-content flow in PRIVACY.md - drop brittle source-substring panel tests and add coverage for a chrome-devtools server with no config args * fix(browser-ext): improve acceptance diagnostics and honest phase naming (#6739) * fix(browser-ext): address review feedback on diagnostics PR (#6739) * fix(cli): stabilize flaky orphan-session transport tests (#6739) Replace hardcoded setTimeout(40ms) + assertion with vi.waitFor() in the session/new and session/load orphan tests. The 40ms budget is too tight under CI parallelism, causing intermittent removeSession-not-called failures. vi.waitFor polls until the assertion holds (default 1s timeout), matching the pattern already used elsewhere in this file. * fix(browser-ext): address review feedback on diagnostics PR (#6739) * fix(browser-ext): address review feedback on diagnostics PR (#6739) * fix(browser-ext): address review feedback on diagnostics PR (#6739) * fix(browser-ext): address review feedback on diagnostics PR (#6739) * fix(cli): restore PAGE_SESSION_ID forwarding for lazy-attach path (#6739) The autoAttachActive gate on PAGE_SESSION_ID command forwarding broke the cdp-ws lazy-attach path, which sends commands with PAGE_SESSION_ID without a Target.setAutoAttach handshake. Revert the forwarding gate to unconditional PAGE_SESSION_ID acceptance while keeping the gated Target.attachedToTarget emission (the Critical fix). * fix(browser-ext): address review feedback on diagnostics PR (#6739) * fix(browser-ext): address review feedback on diagnostics PR (#6739) * fix(browser-ext): make survivor tests load-bearing with log assertions (#6739) * fix(browser-ext): address review feedback on diagnostics PR (#6739) * fix(browser-ext): address review feedback on diagnostics PR (#6739) * fix(browser-ext): address review feedback on diagnostics PR (#6739) * fix(browser-ext): address review feedback on diagnostics PR (#6739) * fix(browser-ext): address review feedback on diagnostics PR (#6739) * fix(browser-ext): address review feedback on diagnostics PR (#6739) * fix(browser-ext): address review feedback on diagnostics PR (#6739) * fix(browser-ext): resolve review findings on diagnostics tests (#6739) - reject preview-range QWEN_CHROME_EXTENSION_BUILD_NUMBER values at the env var boundary with a message naming the variable, value, and range - assert the package-extension symlink test observably ran main() instead of passing on equality alone when both runs fail identically - add CLI-level tests proving explicit positional roots are scanned and a clean scan exits 0, covering paths the symlink-only tests skip on Windows Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com> Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
fddcda86ed
|
ci: reduce SDK Java runner queueing (#8441)
* ci: cancel stale SDK Java pull request runs * fix(ci): preserve SDK Java push scheduling * ci: route trusted SDK Java jobs to ECS * ci: simplify SDK Java runner routing * fix(ci): provision Maven on ECS Java jobs * fix(ci): clean SDK daemon ECS test state * fix(ci): satisfy SDK Java lint |
||
|
|
07ba18ee81
|
ci: externalize review timeout to repository variables (#8460)
Read review timeout settings from two GitHub Actions repository
variables instead of hardcoding them, so tuning no longer requires
a code change:
QWEN_REVIEW_JOB_TIMEOUT_MINUTES (default: 360)
- review-pr job-level hard cap (was hardcoded 300)
QWEN_REVIEW_MAX_TIMEOUT_MINUTES (default: 300)
- per-review max timeout: validation ceiling, large PR auto-scale,
and fallback comment message (was hardcoded 240 in 3 places)
Constraint: QWEN_REVIEW_JOB_TIMEOUT_MINUTES must stay above
QWEN_REVIEW_MAX_TIMEOUT_MINUTES so retry + comment posting never
hit the job-level cap.
|
||
|
|
72bd3dccc2
|
ci: remove broken legacy scheduled PR triage workflow (#8434)
The Gemini-era scheduled PR triage workflow has been dead weight for a long time: - Its only business value — syncing labels from the linked issue to the PR — never fires: gh exports closingIssuesReferences as a flat array, so the script's '.closingIssuesReferences.nodes[0].number' jq path always errors, the error is swallowed by 2>/dev/null, and every PR falls into the "No linked issue found" branch. The latest production run logged 157 "No linked issue" hits and zero label syncs, despite many of those PRs having linked issues. - LABELS_TO_REMOVE is computed but never applied, PRS_NEEDING_COMMENT is never appended to, and the prs_needing_comment job output has no consumer — the rest of the script is dead code. - It burns 1+N API calls against every open PR every 15 minutes. - The id-token: write permission is a leftover from the Gemini/GCP OIDC era; nothing in the bash script uses it. Real PR triage lives in qwen-triage.yml. Remove the workflow and its script, drop the stale docs section describing behavior it never had, and pin the file into the legacy-workflow regression list. Co-authored-by: verify <verify@local> |
||
|
|
be4f5e0963
|
ci: bump qwen-code-action to 05f8171 (skip redundant install, surface install errors) (#8444)
* ci: bump qwen-code-action to 05f8171 * ci: bump qwen-code-action to 05f8171 * ci: bump qwen-code-action to 05f8171 * ci: bump qwen-code-action to 05f8171 |
||
|
|
89b5aa7a03
|
feat(desktop): bridge Electron users to Tauri updates (#8392)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
npm cache producer / Save npm cache (push) Waiting to run
SDK Java / 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
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
* feat(desktop): bridge Electron updates to Tauri * test(desktop): cover parseArguments validation in electron bridge manifest (#8392) * chore(desktop): address bridge review follow-ups --------- Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> |
||
|
|
2d2bdab2b2
|
feat(ci): expand code owner pool for packages/core (#8347)
Add @doudouOUC to /packages/core/ and @wenshao to /.github/CODEOWNERS so maintainer PRs have more potential approvers available. |
||
|
|
186812694c
|
feat(review): publish evidence images to a user-designated assets repo (#8351)
* feat(review): publish-assets — evidence images for PR review comments
GitHub's API cannot attach images to review comments (the web UI's
drag-and-drop upload has no API equivalent), so a review whose evidence is a
screenshot — a TUI rendering, a before/after comparison — had no way to show
it. New `qwen review publish-assets` hosts evidence images in a
user-designated repository and hands back URLs a comment can embed.
Grew from the maintainer's manual workflow (screenshots pushed to
`pr-assets/<PR>-verify` branches over HTTPS), and inherits the shape of the
skill's only other public write (`submit`) deliberately:
- Designated destination: writes only to QWEN_REVIEW_ASSETS_REPO, an
owner/repo the user set by hand — the reviewed repo for maintainers, a fork
or scratch repo otherwise (fork-vs-in-repo becomes a configuration
difference, not two code paths). A separate variable from
QWEN_REVIEW_SCRATCH_REPO on purpose: that contract forbids PR-derived
content, and evidence screenshots are exactly that. Unset → exit 3.
- Authorised run: the same args-file re-parse and target binding as submit,
now extracted to a shared lib/authorization.ts so the two gates cannot
drift (the target-binding lesson lives in one place). Since an effective
--comment forces high effort, low/medium runs can never publish.
- Images only, capped, all-or-nothing: extension allowlist (SVG excluded — a
script container), per-file and per-batch size caps, one refused file
refuses the batch before anything is pushed.
- Immutable references: files land on pr-assets/<pr>-review via the Contents
API (HTTPS via gh; no clone, no SSH), content-hash-named so re-runs are
idempotent, and every URL is pinned to the commit — a posted comment's
evidence cannot be changed from under it. The web-host /raw/ URL form works
unchanged on GitHub Enterprise.
- Auditable: a manifest names every file pushed and the landing commit,
swept by cleanup with the other review artifacts.
The findings artifact gains per-finding `assetFiles` (local evidence paths)
and `assets` (published URLs); `publish-assets --findings/--findings-out`
publishes everything referenced and weaves the URLs back in, so the comment
builder reads the artifact rather than hand-carrying URLs.
What the command cannot check is stated in SKILL.md instead: image content.
Publish only evidence the review itself produced — never a capture of the
user's own terminal, which can hold an env dump in the scrollback.
Tests: 45 files / 1394 assertions — new suites for the assets naming and
validation rules and the command's gates (refusal without designation,
refusal without authorisation, target binding, branch creation, idempotent
re-run, batch refusal, findings weaving); submit's 42 pass unchanged on the
extracted gate.
* fix(review): publish-assets round-1 self-review — six findings
Round-1 review of this branch, walked with the angles the author-side pass
does not cover:
- submit.ts kept its parseReviewArgs import after the authorization
extraction; vitest does not typecheck, `tsc --build` does, and CI's build
leg failed on TS6133. (The whole first CI round's failures cascade from
this one break.)
- ensureBranch %2F-encoded the slashed ref path; GitHub's documented form is
literal slashes and %2F routes inconsistently across endpoints — a 404
here reads as "branch missing" and turns every re-run into a 422 on the
create. Ref paths are now literal (the branch name is built from a
validated integer, so interpolation is safe); the contents `?ref=` query
VALUE keeps its encoding, which is the correct position for it.
- The authorization gate bound URL-shaped `--comment` arguments against the
ASSETS repo, refusing legitimately authorised runs whenever the assets repo
is a fork rather than the reviewed repo. The shared gate's repo binding is
now optional — submit still always binds it; publish-assets binds the PR
number (and host) alone, with a new optional --reviewed-repo to restore
the stronger binding when the orchestrator knows the reviewed repo.
- URLs were pinned to the last PUT response's commit.sha; on an
identical-content update that field's shape is GitHub's to decide, not
ours to assume. The head is now read from the branch ref after the
uploads — one extra call for independence from the response shape.
- putContent's catch-all retried EVERY failure through the exists path,
answering a 401 with a confusing secondary error from the sha lookup; the
retry now fires only on the 422/needs-sha shape and rethrows the rest.
- --findings without --findings-out silently skipped the URL weaving; it
now warns, and --findings-out implies --findings.
New tests: literal-ref assertion, non-exists rethrow, URL-shaped
authorisation without assets-repo binding, --reviewed-repo mismatch refusal.
45 files / 1399 assertions green; `tsc --build` clean.
* fix(review): publish-assets round-2 — empty-findings no-op, reviewed-repo hint
Round-2 findings on this branch:
- A findings artifact carrying no assetFiles is the ORDINARY case for most
reviews, but publish-assets answered it with exit 3 — a refusal an
orchestrator calling the command unconditionally on every posting run
would read as a failure to repair. It is now a no-op (exit 0,
{published:false, count:0}); a bare --files with nothing named keeps the
exit-3 refusal, because there the emptiness IS the caller error.
- SKILL.md's example now names --reviewed-repo for URL-target reviews, so
the stronger authorisation binding is used where the orchestrator knows
the reviewed repo.
44 files / 1387 assertions green; tsc --build clean.
* test(review): fix invalid two-argument expect in assets.test.ts
Round-3 sweep: vitest's expect takes one argument — the message-style second
argument was a lint error and a weak assertion both. The offending value now
rides inside the asserted object, so a regression names which shape slipped
through instead of reporting 'expected true'.
* test(review): pin the findings schema's evidence-asset validation directly
Round-4 sweep: assetFiles/assets were exercised only through publish-assets'
weaving test — the schema's own rejection paths (non-array, empty-string
entry, empty-array drop) had no direct case, so a validation regression
would have surfaced as a confusing weaving failure two layers up.
* fix(review): address all six findings from the automatic review (R1-1..R1-6)
The /review pipeline's own round-1 findings on this PR, each confirmed and
fixed:
- R1-1 (the real catch): the host-binding check sat nested inside the
`req.repo !== undefined` guard, so a caller omitting --reviewed-repo also
silently skipped the HOST binding — contradicting the documented "binds
the PR number (and host) alone". The host check now stands on its own;
a new test pins an Enterprise-host mismatch refusal with the repo binding
absent.
- R1-2: --pr accepted whatever yargs `type:'number'` passed through (NaN,
0, 3.5), and --user-authorized bypasses the gate that would have
re-parsed the target — `pr-assets/NaN-review` was reachable. A Gate-0
positive-integer check now refuses first, matching submit's sibling
discipline.
- R1-3: the suite drove the skillArgs seam without clearing
QWEN_CODE_SESSION_ID, so running it inside an active Qwen Code session
spuriously failed eight tests. beforeEach now saves/clears the variable
and afterEach restores it.
- R1-4: the 40MB aggregate cap was enforced inline and untested (a mutation
deleting it stayed green). The per-file rules and the total cap now live
in one pure ruling, validateAssetBatch, unit-tested with five 9MB sizes
and no fixtures.
- R1-5: the asset_files snake_case alias was the one untested member of an
otherwise-tested alias family; pinned.
- R1-6: the setGhHost wiring had no command-level assertion; a GHE test now
pins both the call and the host-carrying manifest URLs.
44 files / 1397 assertions green; tsc --build and eslint clean.
* fix(review): address all ten round-2 findings from the automatic review
Round-2 of the /review pipeline on this PR: 2 Critical, 8 Suggestions,
every one confirmed against the code and fixed.
Criticals:
- The round-2 test block added for the empty-findings no-op omitted the
QWEN_CODE_SESSION_ID save/delete/restore its two sibling blocks perform,
so the suite spuriously failed inside an active Qwen Code session — the
exact dogfooding environment this repo reviews from.
- The gh routing and the returned URLs read the host from two different
sources: with --host absent, gh children inherit an operator-exported
GH_HOST (routing at Enterprise) while rawAssetUrl defaulted to
github.com — every returned URL a 404. One effectiveHost (flag, then
GH_HOST env) now feeds both.
Suggestions:
- putContent's retry discriminator matched a bare `422` anywhere in
err.message — which execFileSync fills with the full command line,
including the PR-numbered remote path: evidence for PR #4220 would read
a 401 as "already exists". Anchored to `HTTP 422`.
- ensureBranch's bare catch read every ref-lookup failure (401, 403
rate-limit) as "branch missing"; only HTTP 404 takes the create path
now, and an empty assets repo — whose default_branch resolves while its
head ref 404s — is named as the condition it is, with the fix stated.
- Validation refusals threw (yargs exit 1, stack trace, empty stdout)
while every other gate in the command answers exit 3 +
{"published": false}; unreadable files and batch refusals now speak the
same refusal language.
- The command's idempotent writes (content-hashed PUTs, a ref create
whose duplicate is tolerated) now go through a new ghWithInputRetried —
sharing gh()'s transient-error retry — and ghWithInput's no-retry
docstring names the two-caller split instead of claiming a sole caller.
- parseAssetsRepo admitted dot-segment repos (`owner/..`) its docstring
claimed were path-safe; segments now exclude `.`/`..`, mirroring
submit's isRepo.
- stringArray accepted whitespace-only evidence paths; trim(), matching
the sibling asString.
- The GHE test asserted setGhHost was called but not WHEN; it now asserts
the call precedes the first API invocation.
44 files / 1403 assertions green; tsc --build and eslint clean.
* refactor(review): one refusal helper for every publish-assets gate
Round-2 of this branch's fresh review: the refuse() helper existed below
seven inline copies of the identical three-line refusal — the drift shape
where one site eventually forgets the exit code. Hoisted to the top of
runPublishAssets and used by every gate; message content unchanged where
tests pin it. 26/26; tsc clean.
* fix(review): address the round-3 review — bidirectional host binding and 14 more
The automatic review's third round on this PR: 1 Critical + 14
Suggestions, each verified and addressed.
The Critical (host binding, both halves):
- The gate's `req.host &&` guard bound the host in one direction only —
an Enterprise-URL authorisation admitted a host-less write routed at
github.com (or wherever GH_HOST pointed). The gate now compares the
authorised host against the write's EFFECTIVE host, defaulting an
absent req.host to github.com: a host is a host, not an exemption.
- Both callers fed the gate the flag rather than the route: publish-assets
computed effectiveHost (--host ?? GH_HOST) AFTER the gate and bound
args.host; submit bound args.host while its gh child inherited GH_HOST.
publish-assets now resolves effectiveHost before Gate 2 and binds it;
submit binds the same resolution.
The rest:
- pr-assets/<N>-review registered in the asset-branch cleanup workflow,
per its own every-producer-must-be-added-here rule — a branch nothing
deletes is permanent.
- ghWithInputRetried had been inserted between ghWithInput and its JSDoc,
leaving the does-NOT-retry comment attached to the function that DOES
retry; each function now carries its own doc.
- putContent's retry-path contents-GET is wrapped: when the 422 was not
the sha-missing shape and the path does not exist, the GET's 404 no
longer replaces the PUT error the user needs.
- stringArray treats null as absent like every sibling parser, so an
artifact rendering "no assets" as null canonicalizes instead of
crashing.
- Test isolation, all four describe blocks: GH_HOST save/delete/restore,
setGhHostMock.mockReset (a sibling's persistent throwing implementation
survives mockClear — the malformed-host test also switched to
mockImplementationOnce), and full mock resets in the blocks that lacked
them.
- The two regression-pin tests the review measured vacuous now
discriminate: each fails only the one call under test and asserts the
pipeline stopped THERE (no contents PUT after a bad create; exactly one
gh call after a 403 lookup).
- New positive pins: a double-fired create ("Reference already exists")
succeeds; the canonical report shape this command's own --findings-out
writes round-trips; an Enterprise-URL authorisation refuses a host-less
write while a github.com-URL one passes it.
Not changed: the finding that reverting the Finding-interface hunk leaves
tests green — the fields are type-level and their removal fails
tsc --build (the CI leg that caught this branch's own TS6133); a runtime
pin would duplicate what the round-trip tests already exercise.
47 files / 1495 assertions green; tsc --build 0 errors; actionlint clean
on the cleanup workflow.
* fix(review): address the round-4 review — empty-GH_HOST passthrough and four test pins
Round 4 came back COMMENTED (down from CHANGES_REQUESTED), 5 Suggestions,
0 Criticals — all five confirmed and fixed:
- An exported-but-empty GH_HOST ("" from an unset workflow var) survives
`??`, being non-nullish: effectiveHost became "" and the gate compared
the authorised host against "", refusing a legitimate github.com write.
Both call sites now collapse an empty trim to undefined (`|| undefined`,
parenthesized).
- The gate's URL-shaped repo/host binding was exercised only via
publish-assets' suite; submit.test.ts now pins both directions of the
host binding and the repo binding at its own call site.
- ghWithInputRetried had no retry-contract test; gh.test.ts adds the
symmetric block to ghWithInput's does-NOT-retry pin (transient 500
retried once then succeeds; non-transient 401 single call).
- The publish-assets mock aliased ghWithInput and ghWithInputRetried to
one mock, hiding which variant a write used; they are two mocks now,
and the happy path asserts the non-retrying variant is never touched.
- The Prepared interface's dead `name` field is gone.
46 files / 1476 assertions green; tsc --build and eslint clean.
|
||
|
|
563f744329
|
feat(ci): size-aware default timeout for the PR review job (#8377)
The review-pr job ran every PR with a fixed 180-minute default budget. A medium PR (#8241, +1577/-57) exhausted it and was killed at exactly 180 minutes without ever posting its review, while the same review can finish in ~90 minutes on a less loaded runner. When the caller does not pass an explicit --timeout, size the default budget by the diff (additions + deletions): small PRs (<= 300 lines) keep the proven 180 minutes, and anything larger gets the full 240 cap. An explicit --timeout=N still wins, and a failed size lookup falls back to 180 rather than failing the review. Raise the job-level timeout from 260 to 300 so the 240 budget plus the shared retry and comment posting fit with headroom. |
||
|
|
07e35bf02f
|
feat(ci): add repo-hygiene skill and weekly patrol workflow (#7908)
* feat(ci): add repo-hygiene skill and weekly patrol workflow Add .qwen/skills/repo-hygiene (SKILL.md + scripts/run-agent.mjs) and the corresponding GitHub Actions workflow that runs a weekly scan-and-fix patrol every Monday 03:00 UTC. The skill defines a single scan-and-fix mode that scans the repo for small, certain docs/test/code hygiene issues (broken Markdown emphasis, test coverage truthfulness, contract mismatches, real boundary conditions), batches up to 8 fixes as individual Conventional Commits on ONE branch, and writes findings.json + bilingual report-only.md + PR body. High-risk paths (packages/core/src/**, auth/providers/models/config/tools/services) are report-only — they go to the PR comment, never the diff. The workflow implements the trust split: agent never holds GitHub credentials; only the workflow publishes. Includes dedup (skip when a hygiene PR is already open), PAT identity verification, an independent verification gate (build/typecheck/lint + per-package vitest --changed), and a structural gate (commit cap, total diff cap, report-only path assertion). Closes #7383. * docs(repo-hygiene): expand judgment scans to six parallel search angles Port the richer search-angle taxonomy from the operator's interactive loop-hygiene prompt into the SKILL.md. Section B (judgment scans) now enumerates six explicit angles — test-coverage truthfulness, implementation/contract mismatch, resource lifecycle, real boundary conditions, user-visible configuration/API, and docs-as-secondary-scan — each with a grep/code-reference evidence requirement. The previous three-bullet version collapsed resource lifecycle and user-visible config/API into adjacent items, which meant the agent under-scanned AbortController/finally/iterator-return/stream-cleanup gaps and config-vs-schema disagreements in practice. Also makes the subagent workflow explicit: subagents report candidates only and never touch the working tree; the main agent deduplicates and decides. This matches the operator prompt's 'retrieval subagent only reports, never modifies' constraint. * refactor(repo-hygiene): drop deterministic-docs section, fold its principles into the scan loop Section A (Deterministic docs patterns) was too narrow: it gave two specific rg commands that dated quickly and duplicated the docs-as-secondary-scan rule already present in the judgment-scan taxonomy. Remove the section and promote Section B to the only scan-targets list. Two of Section A's cross-cutting principles are preserved by folding them into the scan loop's opening paragraph and the Docs bullet: - 'a pattern hit is a lead, not a finding' — now applies to rg, grep, and any other scanner - 'broken-but-rendering-fine emphasis stays untouched' — now part of the Docs bullet's exclusion list * refactor(repo-hygiene): partition the scan into nine parallel subagents The previous Scan Targets section dispatched subagents by search angle (six parallel scans of the whole monorepo). Each subagent ended up with a shallow read of every file it touched — it couldn't hold a module's contract graph in context, so findings tended to be one-line-deep (docs-vs-code, rendering bugs, dead-code hints). Switch to partition-first dispatch: one subagent per of nine module boundaries (cli/config, cli/runtime, cli/ui, core, extensions, sdk-typescript, sdk-python-java, ui-apps, docs). Each subagent owns its partition, finds the package's own entry points/schemas/registries, then applies the six search angles inside that scope. The main agent deduplicates across partitions afterward. A subagent must stay inside its partition; cross-partition findings are marked crossPartition:true for the main agent to merge or drop. Reading lists are intentionally not prescribed — the subagent picks the partition's own sources of truth. * refactor(repo-hygiene): allow cross-partition tracing, drop anti-padding bullets Two simplifications: 1. Replace 'subagent must stay inside its partition; crossPartition:true' with 'partition is a starting boundary, not a fence'. A subagent may now follow a call chain, import graph, or contract reference into another partition to build evidence. When a finding's evidence lands in a report-only path (core/*, auth/*, providers, models, config, tools, services, cross-package contracts), it goes to reportOnly — never into fixes. This replaces the previous crossPartition handoff machinery with one uniform rule that already exists in Scope Limits. 2. Drop two anti-padding bullets from Scope Limits ('do not batch trivial fixes to reach quota', 'do not manufacture findings to fill the run'). The Shared Rules' 'no speculative edits / keep changes minimal and scoped' already covers the same behavior; restating it here was noise. * docs(repo-hygiene): fix stale 'scans A and B' reference in scan-and-fix mode The A/B section split was collapsed into a single Scan Targets section two commits ago, but step 1 of Mode: scan-and-fix still said 'Run scans A and B'. Reword to describe the actual dispatch: nine partition subagents, six angles inside each, collect and deduplicate across partitions. * refactor(repo-hygiene): switch report-only rule from path to impact scope Drop the path-based report-only whitelist (packages/core/src/**, */src/auth/**, providers/models/config/tools/services/**, cross-package contracts). The report-only trigger is now impact scope: a finding whose minimal fix touches more than three files or more than one hundred lines of production code is report-only. Rationale: the path list was a proxy for 'high-impact area' that over-blocked trivial, certain fixes inside those paths and under-blocked sprawling fixes outside them. The file/line threshold measures impact directly. Scope Limits already caps each fix at 20 lines of production diff, so anything past 100 lines is an order of magnitude beyond that — past the four-file mark is past the single-root-cause test. * docs(repo-hygiene): expand each partition with package layout and correctness criteria The previous Nine-partitions section was a one-line scope pointer per partition. Each partition is now a mini architecture block naming the package path, what the package does, its key subdirectories, and what 'correct' looks like inside the partition. Directory names verified against the current tree (cli/src/{commands,serve,acp-integration,services,remoteInput,dualOutput,startup,i18n,utils,core,export}, cli/src/ui/{agent-view,arena,extensions,mcp,hooks,subagents,background-view,shared,messages,contexts,themes,state,layouts,voice,selection,editors,daemon,models,noninteractive}, core/src/{agents,models,providers,tools,services,prompts,utils,hooks,memory,skills,subagents,permissions,confirmation-bus,mcp,lsp,ide,goals,resources,followup,extension,config,telemetry,output,qwen}, desktop/apps/{electron,viewer}, web-shell/client). * refactor(ci): replace path-based gate with per-commit impact-scope check The workflow gate previously rejected any branch touching core/src or auth/providers/models/config/tools/services paths. This conflicted with the SKILL.md's impact-scope rule (>3 files or >100 lines = report-only). Replace with a per-commit check: each commit must touch ≤3 files and have ≤100 diff lines. This aligns the deterministic gate with the agent's report-only threshold. * chore(ci): allow repo-hygiene workflow on fork for testing * refactor(repo-hygiene): remove fix cap, file report-only as consolidated issue - Remove MAX_FIXES (8) and MAX_TOTAL_DIFF_LINES (150) caps from workflow gate - SKILL.md: no limit on fixes per run, only per-commit scope threshold - Add workflow step to create single GitHub issue for all report-only findings - Issue is deduplicated against existing open hygiene issues - Per-commit impact-scope gate (≤3 files, ≤100 lines) remains unchanged * fix(repo-hygiene): per-fix typecheck prompt + auto-revert on verification failure - SKILL.md: require typecheck after each individual fix, not batched - Workflow: typecheck runs first with auto-revert loop; bad commits are dropped while good ones survive, then full build+lint+test runs - Prevents one bad fix from killing the entire patrol run * fix(repo-hygiene): forbid retrying failed fixes to avoid loop detection Agent got stuck retrying typecheck fixes repeatedly, hitting the per-turn tool-call cap. Explicitly instruct to accept failures as report-only and move on without retrying. * fix(repo-hygiene): use only existing labels, structured failure handling - Remove HYGIENE_LABEL env var; PR dedup by title+author search instead - Issue uses 'bug' label (always exists); dedup by title search - Remove label creation step - SKILL.md: give agent concrete 3-step failure protocol (checkout, move to reportOnly, continue) instead of vague 'don't retry' * fix(repo-hygiene): remove failure protocol, add anti-loop instruction - Remove 3-step failure handling protocol (was over-engineered) - Restore original 'write failure.md' behavior for failed verification - Add: never re-issue identical tool calls (prevents loop detection) * feat(repo-hygiene): add scan/fix split mode for two-phase execution - SKILL.md: add Mode: scan-only and Mode: fix-only sections - Workflow: add mode input (scan-and-fix/scan/fix) + scan_run_id input - run-agent.mjs: add scan and fix specs with proper inputs/outputs - Scan mode: only scans, uploads findings as artifact (~15min) - Fix mode: downloads previous scan findings, only fixes (~25min) - Gate/verify/push skipped for scan-only mode - Branch name resolution skipped for scan-only mode * refactor(repo-hygiene): split into scan + fix jobs in one workflow Two-phase design within single workflow run: - scan job: read-only, dispatches subagents, writes findings.json (~15min) - fix job: depends on scan, downloads findings, fixes + verifies + pushes (~25min) Benefits: - Each phase stays within model's tool-call budget - Scan failures don't waste fix-phase budget - Fix failures don't require re-scanning - phase input: both (default), scan, fix (with scan_run_id) * simplify(repo-hygiene): remove phase/mode inputs, always scan then fix Users just trigger the workflow. Scan and fix jobs run in sequence automatically. No need for phase selection or scan_run_id. * simplify(repo-hygiene): remove Mode sections from SKILL.md - Replace three Mode sections with single 'Execution Steps' heading - Step 1: scan phase (stop after findings.json) - Step 2: fix phase (read findings, fix, verify, write PR) - run-agent.mjs: invocation now tells agent which phase it's in using plain English instead of slash commands * fix(repo-hygiene): use plain expression in job-level if condition Remove ${{ }} wrapper from job-level if conditions to avoid potential YAML parsing issues. * fix(repo-hygiene): configure git remote with PAT before push Checkout uses persist-credentials: false, so git push needs explicit authentication via remote URL with x-access-token. * fix(repo-hygiene): use single quotes for all if conditions yamllint requires single-quoted strings. Changed all double-quoted ${{ }} expressions to single-quoted with escaped inner quotes. * feat(repo-hygiene): gate drops oversized commits instead of failing - Collect oversized commits during scan loop - Capture commit messages before rebase - Use git rebase -i to drop only the bad commits - Move dropped findings from fixes to reportOnly in findings.json - Continue with remaining commits instead of aborting the run * refactor(repo-hygiene): split SKILL.md into per-phase documents - SKILL.md keeps only shared content: rules, scope limits, findings.json format, output contract - scan.md: scan targets (9 partitions, 6 angles) + scan steps - fix.md: fix steps - run-agent.mjs concatenates SKILL.md + phase doc by mode, so each phase's prompt contains only what that phase needs - Drop the unused scan-and-fix mode * refactor(repo-hygiene): move phase docs into references/ per skill convention Match the triage/docs-audit skill layout: SKILL.md is the entry with shared rules and a Workflow routing section; phase details live in references/scan.md and references/fix.md. The CI runner still injects the right phase doc into the prompt; interactive invocations follow the routing section instead. * refactor(repo-hygiene): adopt references/ progressive-disclosure layout Match the triage skill convention: SKILL.md is the single entry with shared rules and a Workflow routing section; phase details live in references/scan.md and references/fix.md which the model reads itself. The runner now sends only SKILL.md plus a one-line phase invocation. * docs(repo-hygiene): drop redundant duplicate-tool-call rule Loop guard already covers real loop scenarios; forbidding same-arg calls at the SKILL layer added no value for a 9-subagent scan with independent budgets and a state-heavy fix phase. * fix(repo-hygiene): enable agent tool for scan and preserve partial findings Scan settings never whitelisted the agent tool, so the nine-subagent dispatch was impossible and the model scanned serially, blowing the 50-minute budget with zero output. Add agent to scan coreTools, teach the serial fallback to write findings.json incrementally after each partition, and upload agent.log/failure.md so failed scans can be diagnosed. * chore(repo-hygiene): drop fork-only repository guard * chore: drop unrelated advisor design draft from branch * fix(repo-hygiene): harden gate, dedup, and verification semantics - Zero-fix runs are now green: fix phase stops silently instead of writing failure.md when the fixes array is empty - PR dedup matches by hygiene/ branch prefix instead of trusting the agent-written title to contain 'repo-hygiene' - Gate drops oversized commits in one rebase, removing the implicit newest-first ordering dependency of sequential rebases - Dropped and reverted commits are appended as notes to pr-body.md so the PR description matches what is actually in the branch - Scan job timeout raised to 75min (agent 50min cap + ~10min setup left a negative margin); typecheck output no longer suppressed * fix(repo-hygiene): raise agent timeout for nine-partition scans The v4 local run timed out at the 50-minute default while the v5 run completed successfully but close to the limit. Nine parallel subagents need more headroom, especially on slower CI runners. - run-agent.mjs default: 50min → 70min - Scan phase explicit QWEN_TIMEOUT_MS: 65min (job limit 75min) - Fix phase explicit QWEN_TIMEOUT_MS: 75min (job limit 90min) * fix(ci): use full SHAs in gate rebase and document typecheck-only revert * fix(ci): skip push when verification reverts every hygiene commit * fix(ci): route dropped and reverted findings into reportOnly * fix(ci): correlate dropped commits to findings by bracketed id * fix(ci): label hygiene PRs with autofix/repo-hygiene * docs(skill): correct scan partition facts and skip empty report-only sentinel * fix(skill): stop requiring report-only.md as a run-agent output * fix(skill): settle the run-agent promise when the log stream errors * fix(ci): gate the fix job on a scan-phase failure.md and validate findings JSON * fix(ci): make the push gate's success dependency explicit * fix(ci): keep dry runs from filing real report-only issues * fix(ci): drop unregistered read_many_files and grant git clean for reverts * fix(ci): count only production files and lines in the size gate * perf(ci): skip the scan phase while a hygiene PR is still open * docs(skill): resolve failure-handling and schema-regen contradictions * fix(ci): apply review fixes to hygiene gates, runner signals, and skill docs * docs(skill): align scan paths, reportOnly schema, and drop dispositions - Document intentionally excluded packages in scan.md - Fix cli/ui subpackage paths to include components/ prefix - Add optional status field to reportOnly schema in SKILL.md - Carry status in workflow gate/verify node scripts - Give fix.md step 4b the same reportOnly drop path as 4a/4c * fix(ci): add fork guard to repo-hygiene dedup job (#7908) * fix(ci): surface surviving hygiene findings when verification fails (#7908) A post-typecheck failure (build/lint/settings-schema/test) exited the verify step non-zero without moving the surviving committed findings to reportOnly, so the consolidated report-only issue never listed them and a persistent failure silently discarded the same findings every week. Mirror the revert path: an ERR trap now moves every remaining fix to reportOnly (status failed-verify) before the step exits non-zero. Also resolve a Scope Limits contradiction in the skill docs: the <=20-line per-fix rule read as a hard cap while scan.md and the workflow gate enforce only the 100-line/3-file report-only threshold. Make <=20 an explicit target and document the new failed-verify status. * fix(ci): salvage committed findings on failure, append to existing report-only issue, and incremental parallel scan writes (#7908) * fix(ci): address review — contracts gate, sandbox image, gate salvage, revert labeling (#7908) * fix(ci): enforce shell allowlist via auto-edit and salvage rebase conflicts (#7908) * fix(ci): address review — scan mkdir allowlist, rg sandbox note, write deny rules (#7908) * fix(ci): deny write_file to .git and persistence vectors in fix agent (#7908) * fix(ci): deny write_file to executable configs and node_modules in fix agent (#7908) * fix(ci): sandbox verification execution and isolate the PAT-bearing push (#7908) * fix(ci): clarify verification sandbox flags harden, not mirror, the CLI defaults (#7908) * fix(ci): sandbox resolve-owning-packages, re-stage gates, deny lockfile writes (#7908) * fix(ci): make hygiene graceful-failure reachable, gate findings schema, scope artifacts (#7908) * fix(ci): disable core.fsmonitor in gate, deny vite.config writes, align scanner wording (#7908) * test(ci): add a repo-hygiene workflow test suite (#7908) * fix(ci): match test assertions to workflow YAML quoting style (#7908) * fix(ci): use double-quoted YAML if-conditions to match test assertions (#7908) * fix(ci): mount hygiene WORKDIR read-only during sandboxed verification (#7908) --------- Co-authored-by: 俊良 <zzj542558@alibaba-inc.com> 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> |
||
|
|
c0196b4226
|
fix(ci): reconcile ECS runner updater on workflow changes (#8373) | ||
|
|
4e7bab8450
|
fix(ci): ask GitHub what the PR changed, not what the base gained (#8372)
The desktop-shell filter used `git diff --name-only FETCH_HEAD HEAD` against a depth-1 fetch of the base. That is a two-tree diff, not the PR's diff: it reports every file the base gained since the branch point as a change on the PR's side. #8132 added packages/desktop-shell, so from then on every branch older than it saw 78 phantom desktop-shell paths, decided the crate had changed, and ran the job -- inside a checkout of refs/pull/N/head, which does not contain the directory. The step then died on a working directory that was never there: Error: The cwd: .../packages/desktop-shell/src-tauri does not exist! Measured on such a branch: 78 desktop-shell paths from `git diff BASE HEAD`, 0 from `git diff BASE...HEAD`, and the job's own log reporting `desktop-shell changed: true` for a PR that touched two files, neither of them the crate. Ask the API instead -- the same call the CI profile classifier a few hundred lines above already makes, so the file now answers this question one way. `previous_filename` is included because renaming a file out of the crate changes it and only the old path says so. Fail-open then needed a floor of its own: "any uncertainty runs the job" is right until the tree has no crate to compile, where it means a red check rather than a cautious one. A head without src-tauri/Cargo.toml cannot have regressed the crate, so the filter reports no change and says why. Driven through the six cases with the step lifted out and `gh` stubbed -- docs-only/touched/absent-crate/API-failure both ways/rename-out -- and the new query run against this PR on the real API, which returns its two files and matches nothing. |
||
|
|
cb2555c7c5
|
feat(desktop): package Web Shell as a release-ready desktop app (#8132)
* feat(desktop): add Web Shell Tauri proof of concept * feat(desktop): prepare Web Shell shell for release * fix(desktop): make release dry runs portable * fix(desktop): harden cross-platform release smoke * fix(desktop): stabilize Windows and Linux CI * fix(desktop): scope bootstrap env to daemon * fix(desktop): stabilize packaged app smoke * fix(desktop): diagnose Linux packaged startup * fix(desktop): address release readiness review * fix(desktop): address follow-up review findings * fix(desktop): address runtime review blockers * fix(desktop): gate cookie auth acceptance behind desktop bootstrap flag - Cookie→Bearer translation middleware now only active when desktopShellBootstrap is enabled - Use timing-safe comparison for bootstrap token validation * fix(desktop): replace cookie handshake with URL fragment auth - Navigate the desktop WebView to /#token=<token>; the fragment never reaches the server, so drop the desktop cookie bootstrap middleware, its cookie->bearer translation, and the related serve tests - Skip the deferred-runtime auth gate for pre-auth Web Shell routes (GET|HEAD / and /assets/*): a document navigation cannot carry an Authorization header, so the fast-path window used to answer the first desktop navigation with 401 Unauthorized until a manual reload - Poll /health?deep=true before navigating: deep health stays 503 (reason: bootstrap) until the runtime app that mounts the Web Shell is ready, so readiness can no longer race the deferred window - Run the folder picker off the main thread and only store the runtime after the WebView navigation succeeds - Enable withGlobalTauri plus a bootstrap capability so the bootstrap page can subscribe to desktop lifecycle events - Update smoke-packaged to assert the fragment contract (unauthenticated root navigation 200, no cookies minted, API routes still 401) and sync the release design doc * fix(desktop): fix Linux smoke log path, add runtime .gitkeep, correct README (#8132) * fix(desktop): close release readiness gaps * fix(cli): keep deferred serve auth gate closed when web shell unmounted (#8132) * fix(desktop): address review feedback on auth gates and runtime bundle (#8132) - Cover the method guard in isPreAuthWebShellRequest: assert unauthenticated POST to / and /assets/* is still 401 during the deferred runtime window. - Add unit tests for is_allowed_navigation covering the unset origin, set origin, and bootstrap-after-origin cases. - Drop DEV:'true' from the release bundle step so the esbuild metafile is no longer shipped as dead weight in the desktop runtime. * fix(desktop): address review feedback on runtime extraction and release workflow (#8132) - Extract .zip Node archives with unzip so Linux cross-builds for win32-x64 no longer crash on GNU tar. - Build the Windows signing config with ConvertTo-Json instead of backslash escapes, which PowerShell treats as a parse error. - Fetch the runtime Web Shell without a bearer token so the smoke test exercises the pre-auth navigation path the shell relies on. - Make GitHub release creation idempotent so a re-run after a partial publish uploads assets instead of failing on the existing tag. * fix(desktop): normalize artifact filenames to prevent updater 404s (#8132) GitHub rewrites spaces to dots when release assets are uploaded, but the updater manifest encoded spaces as %20 via encodeURIComponent. This caused every platform's auto-update URL to 404 on published releases. Replace spaces with hyphens in the Collect artifacts step for all platforms so the local filename, the manifest URL, and the published asset name agree by construction. Update test-release.js fixtures to match and assert no artifact name contains a space. * fix(desktop): address review feedback on security, lint, and code quality (#8132) * fix(desktop): address review feedback on smoke test, error UX, and window state (#8132) * fix(desktop): address review feedback on crate build, recovery UX, auth gate, and CI (#8132) * fix(desktop): address review feedback on settings race, version script, and log growth (#8132) * fix(desktop): address review feedback on retry, auth gate, and release clobber (#8132) * fix(desktop): gate commands to bootstrap origin and show native update dialog (#8132) * fix(desktop): use matches! instead of PartialEq on JoinError result (#8132) * fix(desktop): wait for deferred runtime in smoke tests and sync release flags on clobber (#8132) --------- 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> Co-authored-by: Qwen Code Autofix <qwen-code-autofix@users.noreply.github.com> |
||
|
|
f5b0a85dcf
|
ci: auto-update ECS runners on stable publish and harden update job (#8343)
* ci: dispatch npm-published on stable release and harden runner update The update-ecs-runner-qwen workflow declares a repository_dispatch npm-published trigger, but nothing in the repo ever sent that event, so the self-hosted ECS runners only picked up new qwen releases when someone remembered to run the update workflow by hand. Emit the dispatch from the release workflow after a stable (npm_tag=latest) publish, carrying the released version; dispatch failure only warns so an already-published release cannot be failed by it. Also harden the update job against npm ENOTEMPTY rename failures: all runner processes of a region share one machine, so a concurrent global npm install from another job can race the npm rename of the package dir. Clear stale npm trash dirs and retry up to three times with backoff. * ci(triage): pin action qwen reinstall to the installed version The triage job's qwen-code-action runs an unconditional global npm install with --prefer-offline on every run. On the shared self-hosted ECS box that resolves the latest dist-tag from the persistent npm cache, which lags npm publishes: after the runners were updated to 0.21.3, the next triage job resolved latest as the cached 0.21.2 and downgraded the box, which then also re-created the stale npm trash dir that blocks the update workflow with ENOTEMPTY. Capture the version the Ensure qwen CLI step verified and pass it as qwen_cli_version, so the action's redundant reinstall targets the exact installed version (no dist-tag resolution) and can never downgrade the shared runner. * ci: harden npm-published dispatch and runner update retry (#8343) * test(ci): scope workflow assertions and add triage version-pin test (#8343) * test(ci): pin retry-loop structure in runner update workflow test (#8343) --------- Co-authored-by: qwen-code-ci-bot <qwen-code-ci@service.alibaba.com> 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> |
||
|
|
7100ffb816
|
ci: gate merges on deterministic no-AK E2E (#8313)
* test(sdk-typescript): stabilize permission mode round trips * test(sdk-typescript): make permission control deterministic * test(sdk-typescript): make orchestration E2E deterministic * test(sdk-typescript): make orchestration E2E deterministic * ci: gate merges on deterministic no-AK E2E * ci: gate merges on deterministic no-AK E2E * Merge PR #8302 review follow-up * test(sdk-typescript): distinguish plan-mode enforcement * test(sdk-typescript): distinguish plan-mode enforcement * ci: validate stacked gate against main * ci: validate stacked gate against main * test(sdk-typescript): reuse fake tool server * test(sdk-typescript): reuse fake tool server * test(sdk-typescript): stabilize model switch timing * test(sdk-typescript): ignore E2E side queries * fix(ci): enforce no-AK gate invariants * test(ci): cover no-AK gate event wiring * fix(ci): isolate no-AK gate environment * test(ci): anchor credential isolation checks * fix(ci): complete no-AK gate credential clearing and restore closed-query tests (#8313) * fix(ci): harden no-AK gate per review feedback (#8313) * test(ci): assert timeout-minutes on no-AK gate step (#8313) --------- Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
1843653903
|
fix(ci): ensure qwen CLI before triage action to avoid redundant install (#8337)
Some checks failed
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 / web-shell Browser Regression (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
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
npm cache producer / Save npm cache (push) Has been cancelled
Add a pre-step that checks if qwen is already in PATH (pre-installed by the fleet updater on self-hosted runners) and skips the install. This prevents the action's internal `npm install --global` from hitting EACCES on runners where the non-root user lacks write permission to the global npm prefix. Matches the if-missing pattern already used in qwen-code-pr-review.yml. |
||
|
|
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>
|