mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-25 08:33:55 +00:00
56 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
337da2143c
|
fix(ci): stop dropping agent settings in resolve and follow-up workflows (#9252)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
Security Checks / Dependency CVE audit (push) Waiting to run
Security Checks / Secret scan (TruffleHog) (push) Waiting to run
* fix(ci): stop dropping agent settings in resolve and follow-up workflows Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(ci): pin remaining agent-settings guard gaps from review Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
5b125f0b89
|
fix(ci): minimize spam inline review comments (#9229) | ||
|
|
e7a7ac1bfb
|
feat(autofix): deny-by-default footprint gate and positional window censuses (#9156)
* feat(autofix): deny-by-default footprint gate, positional window censuses, review-loop backlog Follow-up to #8981/#8996, closing the structural causes behind their review-round non-convergence: - Deny-by-default footprint: every file a round touches maps to an AREA (declared workspace, else top-level directory, else the root file itself); areas outside the PR's own footprint are surfaced in a gate-authored advisory, or rejected retryably once the repo variable QWEN_AUTOFIX_FOOTPRINT_ENFORCE is staged to 'reject'. The enumerated class gate keeps rejecting regardless — this inverts the default for everything it cannot enumerate (a denylist is not a boundary). - The three window censuses (PRIOR_TIMEOUTS, WIN_HEADS, PRIOR_HEADS) attribute comments positionally over their own scan-parsed eval markers instead of whole-body win= substrings: a neutralized marker quoted in a handoff excerpt, or any future marker embedding win=, can no longer double-attribute a comment (decoy fixture included; the census fixture's non-numeric round= placeholder is corrected). - BITE_ENFORCE's reply arm inherits the thread root's CHANGES_REQUESTED membership, not just its body tag. - Backlog tests: the bite restore-failure crash contract (verdict-less exit with the rejection document, driven by a ref-deleting runner), merge-base-anchored footprint compares under an advanced main (afterPr fixture hook), and the shrink+bite advisory append order. - SKILL: cap each round's implemented batch (~8 findings, Critical first, defer the rest via comment-replies) — nine review rounds of evidence that oversized fix batches breed fix-of-fix defects — and document the footprint gate. * fix(autofix): close the R1 footprint-gate findings - Advisory lifecycle: one reset at gate start, every writer appends — the footprint advisory no longer dies to the shrink section's rm or its truncating write. - Footprint membership is REF-ANCHORED: areas derive from the pre-round root manifest's workspaces globs (longest ancestor wins, nested workspaces correct), so a round cannot redefine its own boundary and the on-disk resolver is out of this path entirely; non-workspace paths under packages/ keep two segments so sibling projects stay distinct areas; emitted areas are newline-sanitized against phantom footprint grants. - The enforcement knob rides step-level env at both verify gates — $GITHUB_ENV writes from earlier steps cannot downgrade 'reject'. - TESTSIDE's critical() mirrors cr_attached (root and self), keeping enforcement and demotion on one comment set. - Census ownership is LAST-WINS over scan-parsed markers (a stray quoted-or-appended marker cannot double-attribute), the replay decoy is now genuinely discriminating (old whole-body → 0, new → 1), and the growth-gate comment stops citing retired whole-body matchers. Queued per the batch cap: per-line advisory bullets and the third sink charset, discriminating fixtures at the two remaining census sites, the reply-arm bite fixture, freight and merge-base footprint fixtures, and digest-pinning the staged resolver for its remaining consumers. * fix(autofix): close the R2 footprint-gate findings - list_areas reads and translates the ref's workspaces globs ONCE per invocation and matches ancestors in-bash (was ~21ms git+jq+sed per file×ancestor call), emits printf %q keys — line-safe AND injective, so distinct areas can never collapse into one comparison key — and both render arms print one bullet per area from those keys. - Producer failures are a STATE: a failed round- or PR-side diff (orphan history, transient git error) skips the footprint check loudly instead of shrinking one side into a verdict. - The workflow-level FOOTPRINT_ENFORCE env is gone (the step-level pins are the only consumers and outrank it — dead config removed); the two step wirings are count-pinned. - Fixtures: nested-workspace membership discriminates against the packages/ two-segment fallback (sibling nested workspaces stay distinct areas), and the advisory-lifecycle discriminator proves an earlier section's advisory survives the shrink section. Queued: consolidating the six eval-marker regex variants behind one grammar constant (touches six jq programs; its own change). |
||
|
|
4ee6a087e5
|
feat(autofix): judge review-feedback validity by content, not author (#8996)
* feat(autofix): judge review-feedback validity by content, not author
Wrong feedback drives wrong rounds regardless of who wrote it: maintainers
increasingly draft comments with models, so author identity carries no
correctness signal. The trust gate stays as the injection/authorization
boundary it always was, but the validity layer becomes source-blind and
execution-based, enforced by the verification gate rather than prose.
Three mechanisms:
- Bite check: a round changing both source and tests has its changed tests
re-run against the pre-round tree (origin/<branch> sources with the
round's test files overlaid). All green there means the claimed defect
never reproduced — the shape of a plausible-but-false finding implemented
as a fix — and the round is rejected, non-retryable, with the measurement
in LAST_REJECTION so the next round can decline or escalate the finding.
Fails open on every scope limit: single-workspace rounds only (gitignored
dist carries the round's build across the detach, the same confound that
A/B-exempts typecheck), runnable unit tests only, and any pre-round
failure counts as biting.
- Sensitive-area footprint: a round may not expand into CI/verification
machinery the PR itself never touched — .github/, .husky/, eslint/vitest/
tsconfig configs, and the scripts section of existing root or first-level
workspace manifests (the gate's own command surface). Judged by area
class so takeover on an infra PR keeps full freedom; round-added
workspace manifests are exempt. Rejected retryably (the repair pass can
revert).
- Test-deletion advisory: shrinking coverage is surfaced by a gate-authored
section in the round report (deleted files, net test lines), never by the
agent's own prose, so a maintainer reads the agent's justification next
to the machine measurement.
SKILL.md rewrites the address-review protocol to match: identical
verification for every author, probe evidence outranks any assertion,
refuted maintainer claims are escalated with the measurement instead of
silently obeyed or overridden, and severity tags alone no longer make an
item Required — the claim must be checkable and reproduced.
* fix(autofix): harden the validity gates per review round
- Scan round/PR diffs NUL-delimited with --no-renames: a rename out of a
sensitive area now classifies the vacated source path (moving a
workflow out of .github/ is a removal of verification machinery), and
specially named files are no longer core.quotePath-mangled past the
case patterns.
- Narrow the capability classes: .github/workflows|actions, .github/
scripts, and passive .github metadata are separate areas (an
issue-template PR no longer licenses workflow rewrites), and the
transitive executable surface — repo scripts/ (minus scripts/tests/)
and .npmrc/.nvmrc — joins the protected set.
- Gate the bite consequence on machine-read intent: rejection now
requires the round to RESOLVE a Critical-tagged or CHANGES_REQUESTED
finding (resolved-comments.txt matched against rc.json/rv.json);
every other src+test round gets a gate-authored advisory on all-green
instead — a behavior-preserving refactor pinning existing behavior is
no longer rejected.
- Drop the blanket *.md exclusion from bite source detection: skill
markdown is executable agent behavior, and the intent gating now keeps
doc-only rounds safe from rejection.
- Sanitize deleted-test filenames in the gate advisory through a safe
character set: a backtick in a legal git filename could close the code
span and forge gate-authored markdown.
- Replace per-path basename spawns with parameter expansion.
- Tests: rename-evasion, metadata-vs-workflow class split, repo-scripts
class with the scripts/tests carve-out, filename-forgery rendering,
enforce-vs-advisory bite consequences (Critical tag and CR review),
and tree-state-proving runners that flip on pre-round source with the
round's test overlaid (plus the round-leak negative control).
One reviewed finding is declined with evidence in the thread: existential
batch semantics for mixed Critical rounds (per-behavior probe binding
needs test-result parsing; documented as a known limit at the check).
* fix(autofix): close the round-2 validity-gate findings
Sensitive-area scan: read NUL records directly (no tr re-mangling — a
newline filename cannot mint phantom footprint grants); resolve declared
workspace manifests and workspace-root configs through the trusted
resolver (nested workspaces protected, src-tree scaffolds exempt); split
root vs workspace manifest classes; guard the root workspaces array; give
the loop's own workflow and gate script their own class; classify .qwen/
(skills are executable agent behavior); anchor footprint content compares
at the merge base; sanitize violation paths in the rejection document.
Bite check: tolerate rc:-prefixed and CRLF resolved-comment ids (the
handle format SKILL prescribes — enforcement never fired without this);
count replies resolved in Critical-rooted threads as defect claims; skip
non-vitest workspaces (a vacuous --if-present pass must never reject),
self-package-name imports (dist confound), and rounds with paths outside
the resolved workspace; include renamed tests and changed snapshots in
the overlay; drop nested fences from the rejection document; surface
test-only defect claims as an advisory; document the already-fixed
re-raise limit and steer it to a no-code round.
Tests: classifier probe over every arm, footprint cases for the new
classes, enforce-vs-advisory negatives, reply-root enforcement, and the
rc:/CRLF handle round-trip.
* fix(autofix): close the round-3 Critical findings on the validity gates
- Gate-consumed helper scripts (resolve-owning-packages, settings-schema
and contracts checks) join the autofix-loop class: an unrelated
.github/scripts footprint no longer licenses rewriting machinery the
gate executes.
- Skip round-scan files whose content equals current origin/main: a
round that merges main (the flow SKILL prescribes on conflicts) made
ROUND_RANGE degenerate and attributed all incoming main churn to the
round, false-rejecting ordinary base updates.
- Round-added workspace-root configs are the round's own surface (same
cat-file exemption manifests have); deleted workspace manifests are
classified from pre-round existence instead of the on-disk resolver
that can no longer see them.
- The bite vitest guard reads the PRE-ROUND manifest — the tree whose
test script the detached runner actually executes.
* fix(autofix): close R4 validity-gate findings — gate-consumed surfaces join the taxonomy
- Supply-chain surfaces classify: lockfiles/shrinkwraps (root and nested)
and patches/ (patch-package runs on every install) as supply-chain;
.gitattributes (root and nested) as measurement-config — a -diff rule
could blind numstat-based advisories.
- manifest_scripts_changed inspects resolution fields too: workspace
manifests compare {scripts, exports, main, types}; the root manifest
adds exports alongside workspaces.
- resolve-sandbox-image.mjs joins the autofix-loop class (it establishes
the loop's isolation boundary).
- The noop path emits verified_head, making the prescribed no-code
re-verification round mechanically able to resolve threads.
- The bite transcript is cleaned at gate start like its sibling logs;
the advisory's test definition aligns with the growth brake's six
globs (__tests__/, test-utils/ included).
R4-3 (post-round on-disk workspace resolution racing a same-round
workspaces negation) is declined in-thread: it requires the PR footprint
to already license manifest-scripts-root, which is the accountability
boundary working as designed; pre-round-tree resolution is queued with
the census follow-up. R4-5 (advisory in failure paths) queued likewise.
* fix(autofix): deflake the bite harness and align the test taxonomy
- Isolate fixture git from ambient global/system config (the sibling A/B
fixture's GIT_CONFIG_GLOBAL=/dev/null pattern) and fail loudly on spawn
errors with the exit status in the assertion message — the advisory
sub-case intermittently died spawn-level under load with empty streams
and no diagnostic (reproduced 1/6 locally, once on CI).
- BITE_SRC excludes __tests__/ like the gate's own TEST_PATHSPEC.
- SKILL's boundary enumeration names the supply-chain and
measurement-config classes and the full protected manifest fields.
* fix(autofix): close R6 validity-gate findings
- Test-side defect claims take the advisory arm: when every resolved
Critical thread sits on a test file (rc.json .path), the fixed test
legitimately passes pre-round — enforcement grade 'advisory', never a
rejection; the test-only advisory also no longer requires a matching
*.test.* glob (snapshot-/helper-only resolutions surface too).
- Classifier arms: newline-bearing paths fail CLOSED as their own class;
qwen-pr-safety-precheck.yml + pr-safety-precheck.mjs join autofix-loop;
nested .npmrc/.nvmrc; eslint.legacy-filenames.mjs (imported by the lint
leg's config); root manifest filter carries main/types.
- The self-import dist-confound guard matches the package name delimited
(quote or subpath), so @qwen-code/qwen-code no longer swallows its
-core sibling's imports.
- Test isolation extends to the footprint and advisory spawns (R5's
rationale applied everywhere), spawn errors fail loudly there too, the
classifier probe pins the supply-chain/measurement-config arms, the
coverageOnly fixture asserts the advisory text, and the neutralization
ledger header matches its count.
- The resolve-threads design doc records the widened no-op
verified_head rule and its safety argument.
Deferred to the backlog per the convergence note: the bite-side restore
crash-contract test (shared-fixture work), origin/main-advanced footprint
fixtures, and advisory append-order pins.
* fix(autofix): close R7 validity-gate findings
- The resolve/reply pass is a shared function serving BOTH the pushed and
no-op outcomes: the no-code re-verification escape can now actually
resolve threads, and no-op declines finally post their in-thread
replies (a pre-existing silence gap). The design doc states the shared
path, its guards, and the named first-round residual.
- TESTSIDE demotion votes only over resolved CRITICAL threads (a source
Suggestion resolved alongside no longer breaks it; a source Critical
alongside keeps full enforcement) — three fixtures pin the matrix.
- The shrinkage advisory measures with --no-renames (a rename out of
runner discovery is a shrink) and NUL-safe deleted names.
- Bite inputs pass through the merge-freight filter the class scan
already applies, and BITE_SRC collects NUL-safe.
- Demoted rounds get their own advisory text (all-green is their
expected shape, not a failed reproduction).
- The manifest block comment matches the resolver-backed code; the
footprint and advisory test spawns get the isolation and loud
spawn-error handling previously claimed — the R6 reply overstated
that fix and this commit is the correction.
* fix(autofix): close the review-body re-checks on the validity gates
- Deleted-manifest classification honors the fixture exemption from the
PRE-ROUND root manifest's workspaces globs (was_workspace_dir) — a
deleted src-tree fixture manifest is no longer false-rejected, while a
deleted declared workspace still classifies; the PR-footprint scan
gets the same treatment anchored at the merge base, so a PR-deleted
workspace keeps licensing later rounds.
- A config added into a PRE-EXISTING workspace is machinery (the gate's
legs execute it); only a config born with its round-added workspace
keeps the exemption.
- The shrinkage advisory applies the merge-freight skip per file (NUL
numstat records), so a base-merging round is not charged main-side
test churn in trusted-voice text.
- The bite rejection document renders filenames through the safe
charset and collapses backtick runs in the runner tail below the
outer fence length.
- AGENTS.md/CLAUDE.md classify as agent-policy; the root-manifest
comparator covers lint-staged and config (sandboxImageUri) too.
Still standing by recorded design, acknowledged in the review body:
R1-9 (already-fixed re-raise), R1-27 (existential batch semantics),
R4-5 (post-round resolver vs same-round workspaces negation).
* fix(autofix): close the round-9 validity-gate re-checks
- TESTSIDE's critical() carries the CHANGES_REQUESTED review-state arm
and receives rv.json, mirroring BITE_ENFORCE — a CR-enforced test-side
claim demotes to the advisory arm, and a CR-enforced source claim can
no longer collapse into it (R8-1, both directions).
- was_workspace_dir matches workspaces globs PATH-AWARE ('*' stops at
'/', '**' spans, '?' single, '!' entries skipped conservatively): a
nested src-tree fixture manifest deletion no longer false-rejects
while a declared workspace deletion still classifies (R9-1); both
pinned by fixtures.
- The PR-footprint manifest arm answers aliveness and membership from
refs (origin/<branch> / merge base), never the round's on-disk tree —
a PR-added workspace a round later deletes keeps its footprint class
instead of walling the deletion (R9-3).
---------
Co-authored-by: verify <verify@local>
|
||
|
|
8189c284ae
|
fix(ci): use repository token for spam minimization (#9140) | ||
|
|
85a06bbec8
|
fix(desktop): bridge Electron users on Windows and Linux (#9079)
* fix(desktop): bridge Electron users on Windows and Linux * test(desktop): satisfy bridge contract lint * fix(desktop): harden Electron bridge migration * chore(desktop): note sibling-script regex sync for bridge artifacts * fix(desktop): satisfy release manifest lint * fix(desktop): validate Electron uninstaller path before migration The migration ExecWait target is assembled from the HKCU InstallLocation registry value; require the uninstaller executable to exist before running it, and pin the conjunct in the release contract suite. |
||
|
|
5a6452a2a5
|
fix(ci): make autofix verification gates hermetic to runner git config (#8961)
* fix(ci): make autofix verification gates hermetic to runner git config A leaked global exec knob on the persistent pool (run 31516789251: diff.external=global-driver in the runner user's ~/.gitconfig) failed four per-hunk probe tests in packages/cli on #8613. The rejection was charged to the round (package tests are A/B-exempt), which burned the 18-minute repair on a failure no repair can reach and ended the round as a timeout — attempt 4 of the failure window, with nothing pushed. Three layers against that class: - Both verification gates (the review script and the issue-fix inline twin) now export a per-run throwaway GIT_CONFIG_GLOBAL (seeded with the workspace safe.directory) and GIT_CONFIG_SYSTEM=/dev/null before their first git command, so every check they spawn — vitest fixture repos included — is hermetic to the host, and a branch-authored `git config --global` dies with the run instead of poisoning the next one. - The sanitize step (all three byte-identical copies) now also scrubs the runner USER's global config — denylist of the command-execution families only, so infra-owned routing/credential keys survive. This self-heals the live pollution on the affected runner on its next job and removes (not merely bypasses) a planted global core.hooksPath. - test-efficacy.integration.test.ts gets the same GIT_CONFIG_GLOBAL / HOME isolation as git.integration.test.ts: the code under test pins --no-ext-diff, but the test scaffolding's plain `git diff` did not, so a hostile user git config could fail the suite anywhere. Contract tests pin the gate redirects (before the first git command, truncated per run) and functionally run the extracted scrub pipeline against a poisoned fixture config, asserting the kept/removed sets. * fix(ci): widen the config scrub and re-sanitize before PAT-bearing steps Address the #8961 review findings (2 Critical, 8 Suggestions across two reviews), all probe-verified by the reviewers: - Denylist regex: subsection slots are .+ (git subsection names may contain dots — diff."a.b".command flattened past [^.]+), and the missing exec families are added: gpg.(*.)program, pager.*, interactive.diffFilter, difftool./mergetool., remote.*.uploadpack/ receivepack. The functional fixture now covers every alternation plus dotted subsections, non-exec/corrupt/missing-config arms pin the two load-bearing '|| true' guards. - The global scrub moved above the .git early-exit: host hygiene owes nothing to the workspace existing. - New resanitize-git-config.sh (staged from the trusted base) re-runs the local allowlist sweep and the global denylist scrub at the top of both PAT-bearing git steps — the gates run branch test code on the host after the job-start sanitize, and the env redirect is not a filesystem boundary. Contract tests pin script/step lists equal, the staging in both jobs, the call-before-credential ordering, and run the script functionally against planted local+global keys. - All three one-shot credential helpers lead with -c credential.helper= (empty resets the helper list; the first helper to answer wins, so a planted one must never run first). Count-pinned in the contract test. - comment-status.integration.test.ts gets the same git-config isolation as its siblings; test-efficacy gains an isolation tripwire test that goes red if the redirect is removed, instead of only on hostile hosts. - Comment fixes: the copies are cross-referenced as contract-test-pinned, and the system-config bypass is documented in both gates. * fix(ci): close the XDG/env/transport bypasses around the config scrub Address the #8961 round-2 review findings (5 Critical + 8 Suggestions, probe-verified by the reviewers; the XDG listing gap independently reproduced on git 2.55): - The global scrub sweeps BOTH files of the global scope: with ~/.gitconfig and $XDG_CONFIG_HOME/git/config both present, `git config --global` lists/unsets only the former, so keys planted in the XDG file survived every copy. The scrub is now a loop that redirects GIT_CONFIG_GLOBAL at each file in turn. - Denylist adds url.*.insteadOf/pushInsteadOf (transport rewrite of the PAT push/fetch; rest of url.* stays) and http.*.sslVerify/sslCAInfo (turns a kept http.proxy into a TLS-terminating interceptor); the three PAT helper chains lead with -c http.sslVerify=true. - The staged resanitize script's provenance holds at cp time only — RUNNER_TEMP is writable by the branch code that runs in between — so the staging steps record its sha256 in GITHUB_OUTPUT and the PAT steps verify before executing. - Both gates and both PAT steps export GIT_CONFIG_COUNT=0: GITHUB_ENV-injected GIT_CONFIG_KEY/VALUE entries apply at command-line precedence and outrank every file-level guard. - Gates emit a ::notice when /etc/gitconfig exists (bypassed by the redirect — replicate needed settings via per-job env). - Tests: the scrub's functional harness drives HOME/XDG fixtures and covers the new families; the resanitize run plants worktree-scoped config (deleting the rm -f line previously stayed green); the gate redirect block is executed against a hostile HOME and an env-planted GIT_CONFIG_* key; the isolation tripwire pins the NOSYSTEM leg and probes system-scope leakage. - The process-env git isolation pattern is extracted into isolateHostGitConfig() in review/lib/test-utils.ts and adopted by all five suites that duplicated it; comment-status gains the same tripwire. * fix(ci): take PAT git steps off host scopes and close the env channels Address the #8961 round-3 review (5 Critical + 6 Suggestions, probe-verified by the reviewer): - Both PAT-bearing steps now run fully hermetic, same shape as the gates: a per-run throwaway GIT_CONFIG_GLOBAL + GIT_CONFIG_SYSTEM= /dev/null, so a concurrent job rewriting the shared ~/.gitconfig in the sweep->push window (max-parallel, one HOME across ~27 runner registrations) can no longer steer the push, and a URL-scoped sslVerify=false there can no longer override the -c pin. Both steps and both gates also strip the git ENV channels that outrank file config: GIT_CONFIG_PARAMETERS, GIT_SSL_NO_VERIFY/CAINFO, GIT_PROXY_COMMAND, GIT_EXEC_PATH, GIT_DIR/WORK_TREE, GIT_ASKPASS, GIT_SSH/_COMMAND, plus GIT_CONFIG_COUNT=0. - The push-race salvage merge runs -c commit.gpgsign=false: a global commit.gpgsign=true with no key would exit 128 and be misread as a content conflict, discarding a verified round (R2-10). - The maintainer-fork fetch, the one PAT-bearing network site the round-2 rollout skipped, leads with -c http.sslVerify=true -c credential.helper= (anonymous; public fork heads need no auth, so it fails closed on a 401 instead of feeding a planted helper the PAT). - Denylist widens protocol.ext.allow to protocol.(ext.)?allow (the top-level fallback policy arms ext:: too) in all four copies. - Tests: the two PAT hermetic blocks and the two gate blocks are pinned equal; the sha256 verify line is pinned verbatim and asserted to carry no bypass; the resanitize fixture plants a live XDG exec key (drops of the loop's XDG leg now fail); the gate redirect functional exec adds the env-channel unsets; diff-plan adopts isolateHostGitConfig (sixth suite) keeping its GIT_TERMINAL_PROMPT delta; comment-status tripwire gains the GIT_CONFIG_GLOBAL assertion. * fix(ci): pin PATH, seal repo-redirect and env channels, harden all PAT sites Address the #8961 round-4 review (6 Critical + suggestions, probe-verified by the reviewer): - PATH is pinned to a value the stage step records before any branch code runs, and LD_PRELOAD/LD_AUDIT/LD_LIBRARY_PATH are dropped, at the top of every PAT step and both gate steps — a $GITHUB_ENV-planted PATH or preload would otherwise swap the git/sha256sum/bash the digest gate itself runs on. - The Prepare step (PAT-bearing, previously unhardened) now takes the same hermetic preamble as the push steps; all three PAT preambles are pinned identical by the contract test. - The throwaway global config is created with mktemp, not a fixed literal path a same-user watcher could re-plant into after the seed. - The env-strip list gains GIT_ALLOW_PROTOCOL (env twin of protocol.allow), GIT_COMMON_DIR / GIT_OBJECT_DIRECTORY / GIT_ALTERNATE_OBJECT_DIRECTORIES / GIT_SHALLOW_FILE (repo-redirect twins), across all PAT and gate copies; the salvage/fork fetches carry -c fetch.recurseSubmodules=false -c protocol.ext.allow=never so a planted submodule cannot execute an ext:: URL with the PAT. - resanitize removes .git/commondir and .git/shallow (the file twins of GIT_COMMON_DIR/GIT_SHALLOW_FILE), and Push-and-report refuses to push a HEAD that is not the gate's recorded verified_head — closing the repo-redirect path that pushed attacker content. - The gate runner (run-autofix-review-verification.sh) is now digest- verified before both gate passes, like resanitize already was: the branch runs its own build/test between the passes, so an unverified copy would let it define its own verdict. - Contract tests pin every new surface: the three identical PAT preambles, the full unset var set, the mktemp path, the trusted-PATH wiring, the two gate-runner digest checks, the recurse/protocol pins, the HEAD==verified_head guard, and the commondir/shallow removal. * fix(ci): pin gh env channels, harmonize allowlist subsection slots Round-4 follow-ups: - Pin GH_HOST=github.com and unset GH_TOKEN/GH_ENTERPRISE_TOKEN/ GH_CONFIG_DIR before the first gh call in all three PAT steps, so a $GITHUB_ENV-planted GH_HOST cannot spoof the identity check and a planted GH_TOKEN cannot outrank the inline one. - Harmonize the local allowlist's remote/submodule subsection slots to .+ (matching the denylist comment and preventing a dotted-name remote from silently losing its url/fetch on every resanitize). * fix(ci): pin gh config dir and push the exact verified object Round-5 closable findings before landing: - R5-7: pin GH_CONFIG_DIR to a fresh mktemp -d instead of unsetting it, so PAT-bearing gh calls no longer fall back to the attacker-writable ~/.config/gh (whose config.yml can carry http_unix_socket and other transport reroutes) on the shared HOME. - R5-8: push the exact verified commit object (PUSH_SHA:refs/heads/...), not symbolic HEAD which the push would re-resolve — closing the check-then-use race the verified-HEAD guard was added to close. PUSH_SHA is pinned to VERIFIED_HEAD under the guard and re-pinned to the merge result after each salvage merge. The remaining round-5 Criticals (BASH_ENV/BASH_FUNC_* and LD_PRELOAD executing at step-shell startup before any unset runs; GITHUB_OUTPUT writable by gate-run branch code) are not closable from inside a Actions step — they require runner-level isolation and are tracked as a follow-up. |
||
|
|
a32ec1ee4a
|
feat(desktop): add Aliyun OSS release mirror (#8976)
* feat(desktop): mirror releases to Aliyun OSS * fix(desktop): harden OSS mirror workflow and tests - Add ref guard to sync-desktop-to-oss.yml (dispatch only from main) - Add diagnostic error messages for missing Windows/Linux installers - Harden test: pin verify-index > 0 before ordering comparison - Harden test: pin confirm-before-publish ordering and source comparison - Add test: stable-only release validation in reusable sync job * fix(desktop): harden OSS mirror permissions, stable-version guard, and non-latest repair - Remove workflow-level actions:read; grant it only to the sync-oss caller job - Reject suffixed versions for published stable releases in prepare - Turn latest-feed comparison into a non-fatal check; condition publish/verify on match - Assert both check_for_update call sites in release test - Add jq stable-only guard assertion and endpoint default alignment test Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
3a59332361
|
fix(ci): seed the dist-rebuild warning on every retryable A/B exit (#8958)
* fix(ci): seed the dist-rebuild warning on every retryable A/B exit The baseline leg rebuilds dist/ from baseline sources, and every retryable exit of the verify gate hands that tree to the repair agent — but the "run npm run build first" steering note only reached the green-baseline rejection. The comm -23 comparison failure and different-signature exits sent the repair agent in blind, free to trust or test against stale baseline artifacts (the different-signature exit carried the note on #8765's branch; the #8878 port kept it on the green exit only). #8765's post-close round-3 review flagged the comm path as Critical. Append the note on both missing exits and pin all three paths: the DIFFERENT-reason test now asserts the note, a new test stubs comm to fail and asserts the same, and the pre-existing test pins the note OUT of its document — no repair runs for that verdict. Mutation-tested, 3 of 3 caught: comm-path note dropped, different-reason note dropped, note leaked into the pre-existing document. * fix(ci): single emit point for the dist note, name the comm-failure exit Address the two review suggestions on #8958: - The steering note existed as three byte-identical copies, and the "every retryable exit seeds the note" invariant depended on copy-paste — the exact drift this PR was patching (one exit seeded on #8765's branch, one lost in the #8878 port). Both reviewers flagged it. The string now lives in seed_dist_note(), called from all three exits. - The comm-failure exit seeded the note but, unlike its sibling retryable exits, emitted no verdict-rationale line — an oncall could not distinguish "the comparison itself failed" from "baseline is green" without re-running the A/B. It now says so. Mutation-tested: mutating the string inside the helper fails all three path assertions at once; mutating the rationale line fails the comm-exit test. * test(ci): pin the no-identity baseline arm of the A/B gate (#8958) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
5dc98240c7
|
fix(ci): port the verify gate's remaining hardening from #8765 (#8878)
#8816's branch accidentally carried #8765's early commits, and the takeover loop evolved the gate further there (subset identity via comm -23, the retryable third arg, subset fixtures) — so #8765 closes as subsumed, and this PR ports what main still lacks: the two improvements its reviewers named for porting, plus the open round-6/7 findings that survive on main's gate. - Pre-detach short-circuit: an empty head signature (vite/esbuild/ crash — the KNOWN LIMIT class) fails closed regardless of the baseline, so decide it BEFORE paying the detach + full baseline re-run + restore. - Build-dirt guard: the A/B'd build REWRITES a tracked file (the vscode companion settings schema), and the undiscarded rewrite makes either checkout refuse — degrading a real verdict into the crash path. `git restore -- .` before both checkouts; tracked-only, and the tree was asserted clean before the deterministic checks. - Restore-failure semantics: a plain outcome=failed is an EVALUATED rejection — the watermark advances and a transient git failure strands the item as a permanent human handoff. The gate now leaves outcome unset (the gate-crashed path retries next scan) and still writes the detail document so the crash comment explains itself. - The dist-rebuilt steering note seeds the repair feedback on both retryable A/B exits — the repair agent's only warning that dist/ holds baseline-built artifacts. - The stale-base retry handoff prefixes its embedded rejection with a the-base-has-moved note, so the retry agent is not steered toward no-action by framing written before the auto-update. - The two A/B side logs joined the repair step's cleanup list. - Tests: identity-less short-circuit, tracked-dirt survival, verdict-less restore crash, long-preamble render cap, PREEXISTING clause selection through the executable report harness, and the stale-framing note pin. Mutation-tested, 5 of 5 caught: short-circuit dropped, restore guards dropped, restore-failure reverted to the evaluated rejection, dist note dropped, stale-framing note dropped. Co-authored-by: verify <verify@local> |
||
|
|
358091833b
|
fix(ci): watchdog silent sandbox hangs and reap the containers they leak (#8816)
* feat(ci): A/B deterministic gate rejections against the pre-round ref A deterministic rejection in the autofix verification gate is only chargeable to the round if the same check passes without the round's commit. The gate charged every red to the fix unconditionally, and run 31276008548 measured what that costs when the premise is false: PR 8614's branch predated #8693's tsconfig guard while node_modules came from the post-#8693 trusted base, so `npm run build` was equally red at origin/<branch> — 63 minutes of accepted agent work discarded, an 18-minute repair burned on a failure the repair agent is forbidden to touch (it may only amend the round's own fix), thirteen rounds in a row, and the same again on the #8616 leg. On rejection the gate now re-runs the failing check at origin/<branch> (the branch as pushed, before the round) in the same environment: - baseline green: today's path exactly — outcome=failed, retryable=true, the repair pass gets its chance. - baseline red too: outcome=failed with preexisting=true and NO retryable. The repair step keys on retryable and is skipped — it cannot reach a failure outside the round's diff by construction — and gate-rejection.md says outright that the branch needs a base update (merge main), which flows into the failure comment as-is. Fail-closed toward today's semantics: any A/B infrastructure problem (missing ref, checkout failure) charges the fix as before, and a restore failure after the baseline run rejects outright since the tree can no longer be trusted. The round's work is still not pushed — this changes the verdict's honesty and cost, not the push policy. Tested by executing the real script in a real two-remote git repo with an npm stub whose failures are keyed by commit SHA: round-caused red (baseline green), pre-existing red (both red), and the untouched green path. Mutation-tested, 3 of 3 caught: skipping the A/B, claiming pre-existing without measuring, and dropping the tree restore. * Address review: bound the A/B to checks it can honestly compare All seven findings verified before fixing; the three Criticals were each a way the A/B compared something other than the check that failed. R1-1 — the contracts check feeds on stdin, which its first run drains; the baseline leg re-ran against EOF and checked an empty file list. R1-3 — the schema check's verdict rides on packages/core/dist, which the core-rebuild guard built from ROUND sources and which, being gitignored, survives the detach. Both checks are now A/B-exempt (run_check_no_ab): their baseline verdicts prove nothing, and their rejections stay where the repair agent can actually act on them. R1-2 — a workspace the round ADDS does not exist at the baseline, and npm exits 1 there with "No workspaces found" (measured; --if-present forgives a missing script, not a missing workspace) — a round-caused failure misread as pre-existing, skipping the one repair that can fix the round's own package. The per-package loop now A/Bs only when the workspace exists at origin/<branch>. R1-4 — a chatty PASSING baseline used to flood the tail -c 3000 evidence window and push the actual failure text out of gate-rejection.md, the sole carrier into the repair feedback, the PR comment, and the next round's LAST_REJECTION. The baseline transcript now goes to a side log and only a FAILING tail is merged back, where it is the evidence. R1-5 — the pre-existing paragraph pushed gate-rejection.md past the report's head -c 3500 cap, truncating the closing fence for branch names past 44 characters. Cap raised to 3900, invariant comment updated with the new arithmetic. R1-6 — preexisting=true had no read site. It now flows verify → Finalize verification → the failure report, whose headline swaps the generic gate clause for "PRE-EXISTING failure … needs a base update (merge main)". R1-7 — the no-round-commit guard was unpinned (deleting it kept all tests green). Now exercised through the core-rebuild path, the one A/B-eligible check that runs before the commit gate. Four new behavioral scenarios (chatty baseline, no-commit round, A/B-exempt checks, round-added workspace) plus workflow pins for the forwarding, the clause, and the cap. Mutation-tested, 4 of 4 caught: schema back to A/B (3 tests), guard dropped, side log reverted, no-commit guard dropped. * Address review round 2: A/B only what it can prove, prove what it claims Ten findings across two rounds, each verified before fixing. The three deepest share one lesson: the A/B is only sound for a check whose inputs travel entirely with the git ref, and whose failure it can IDENTIFY, not merely observe. R2-1 — rc=1 at both legs does not make them the same failure: the branch can fail for reason A while the round fails for reason B, and a baseline infrastructure hiccup is a nonzero exit too. Pre-existing now requires a MATCHING failure identity — tsc diagnostics normalized to file + error code (positions shift with the round's edits), compared via comm(1) on a per-check transcript. No diagnostics on either side means identity cannot be established and the round stays charged. R2-2 / R2-7 — gitignored dist survives the detach carrying the ROUND's build, so any dist-consuming check A/Bs reverted sources against round-built artifacts: package tests (channel-base resolved through dist exports) and typecheck (sdk-typescript resolves core's d.ts — probe-verified three-arm flip). Both are now A/B-exempt, as is lint, leaving `npm run build` — the incident class, and the one check that rebuilds its own inputs from the checked-out sources — as the sole A/B candidate. The workspace-existence guard dissolves with it. R2-3 — the fixture inherited the caller's global git config; a failing global pre-commit hook broke all seven cases. The harness now isolates GIT_CONFIG_GLOBAL/SYSTEM for every git child, and the suite is proven green under a deliberately hostile hooksPath. R2-4 — Finalize verification now selects preexisting from the same attempt whose outcome it selects (repair verification included). R2-5 / R2-8 — the "merge main" advice is now conditional at both layers: the script paragraph states the measured fact and hedges the remedy; the report headline uses the compare the step already ran — behind/diverged gets the base-update clause, an up-to-date branch is told its own pre-round code needs attention. R2-6 — the rejection document now sizes its evidence tail against its preamble (floor 500 bytes, total under the 3900-byte render cap), so the closing fence can no longer be truncated off by a long branch name. R2-9 — dissolved by R2-2: package tests no longer A/B, the guard and its uncovered positive branch are gone. R2-10 — the baseline-evidence merge is now pinned: the pre-existing scenario asserts the baseline leg's own failure line (keyed by its SHA) reaches gate-rejection.md. Eight behavioral scenarios; mutation-tested 5 of 5: identity dropped, typecheck re-enrolled, package tests re-enrolled, evidence merge dropped, fixed tail restored. * Address review round 4: sharpen identity, stage the git failures, sync prose Nine findings, all refinements — the design held, the edges did not. Identity now keeps the diagnostic MESSAGE (file + code collide: two unrelated TS2339s in one file compared equal, skipping a repair that could have shipped — probe-reproduced by the review), and the fixture emits a SHIFTED position on the baseline leg so the position strip is load-bearing instead of decorative (deleting the sed survived every test before; it fails one now). vite/esbuild failures still yield an empty signature by design — documented as the fail-closed limit rather than half-widened. The fail_signature assignments take `|| true`: grep exits 1 on the normal no-match case and survives errexit today only because the caller sits in an if-condition — a future unconditional call site would crash the gate verdict-less. The restore-failure branch is now stageable and staged: the baseline leg recreates (untracked) a file the branch tracks, the checkout back refuses, and the test pins retryable-not-preexisting with the 'could not restore' label. Relaxing the branch to `|| true` fails it. Prose synced to the mechanisms that replaced it: the render-cap invariant restates against the dynamic tail budget (the old 3000-based arithmetic would misguide the next retune), the no-round-commit guard comment names the core rebuild (schema/contracts left the A/B last round), the describe wording counts both A/B-eligible builds, and the pre-existing clauses no longer claim "the repair pass was skipped" — with REPAIR_PREEXISTING forwarded, repair may have RUN; they now state the invariant that is true either way: repair may only amend the round's own fix, so it cannot reach this failure. Mutation-tested, 3 of 3 caught: position strip dropped, message dropped from the identity, restore rejection relaxed. * fix(ci): watchdog silent sandbox hangs and reap the containers they leak Four autofix rounds have died the same way (#8663 twice, #8761 r3, #8763 r4): the agent's last output is the sandbox wrapper's "ContainerName (regular): …" line at docker container entry, then nothing — not one event — until the 2-hour absolute budget kills the round. Four different runners, two image versions: systemic, not a bad machine. Where exactly the container wedges is still unknown (that needs docker state on the runner); what is certain from the logs is the shape — a wedged sandbox produces NOTHING, and a legitimate run is never silent for long (the fleet's longest tolerated quiet is the review pipeline's 10-minute stream-idle window for thinking phases). Two mitigations, each aimed at a measured half of the damage: - run-agent.mjs gains an idle watchdog (QWEN_IDLE_TIMEOUT_MS, default 20 minutes = 2x that longest legitimate silence): zero output for the window kills the agent with a distinct "idle-timeout … the sandbox likely hung at startup" detail, so the failure comment names the right knob and a hung round costs 20 minutes instead of 120. Polled, not reset-per-chunk — a busy stream should not spend its time re-arming timers. - Both sandboxed jobs reap stale qwen-code-* containers at job start: a budget kill reaps the HOST-side docker client, not the container, so every killed sandbox keeps running on the persistent runner — observed directly when a later leg's container-name counter found qwen-code-0.21.8-0 already occupied and picked -1. One job per runner at a time makes any container alive at job start stale by definition. Tested by executing the real run-agent.mjs end to end with stub agents: the hang shape (one line, then silence) dies at the idle window naming the idle limit, and a slow-but-talking agent that outputs every 400ms across a 1500ms window survives to a clean exit — the test that distinguishes a watchdog from a disguised absolute timer. Mutation- tested, 3 of 3 caught: watchdog disabled, last-output tracking dropped (the disguised-timer regression), cleanup dropped from a job. * Address review round 5: the gate's verdict defects and the reaper's live kill Budget-warning round — the five Criticals from both reviewers, no suggestions (each deferred with a recorded reply). fail_signature: `[^\n]*` in an ERE bracket expression does not mean "rest of line" — in POSIX bracket expressions `\` is literal, so it matched "neither backslash nor the letter n" and truncated every tsc message at its first n. Nearly every real message has an early n ("Cannot find name", "is not assignable"), so distinct same-file failures collapsed into identical signatures and a round-caused failure could be labeled pre-existing, skipping the repair. grep is line-oriented: `.*` is exactly the rest of the line. New fixture: two messages differing only after their first n. Pre-existing verdict: the intersection test mislabeled in both directions. A round that ADDS a diagnostic sharing one normalized line with the baseline was called pre-existing (repair skipped for a round-caused, repairable failure); and `comm -12 | grep -q` under `set -eo pipefail` SIGPIPEs comm (exit 141) once the shared output outruns the pipe buffer, charging true pre-existing failures to the round — the exact 18-minute repair waste the gate exists to kill. Pre-existing now means the round's failing set is a SUBSET of the baseline's, and the difference is captured before testing. New fixture: a round adding a second diagnostic to a failing baseline. Restore failure after the baseline leg: was retryable=true with HEAD still detached at the baseline commit — the repair agent works in that very checkout and does no git recovery, so its commit would land on the baseline and be orphaned. Now rejected non-retryable (reject_fix grows a third arg); the next round starts clean from the trusted checkout. The restoreClash test pins the new semantics. Stale-container reap: the premise "a runner runs one job at a time, so any live qwen-code-* container is stale" holds per runner registration, but the filter queries the docker daemon, which is per host — and this pool runs several registrations on one OS. With per-issue/PR serialization only, a concurrent job's sandbox is a substring match away from `docker rm -f`. The reap now takes only provably-dead containers (--filter status=exited/dead, both jobs) and the comment says why a running one is left alone. Preamble printf: the `\`` escapes sat inside a single-quoted format where backslash is literal, so every pre-existing rejection rendered raw backticks instead of code spans (shellcheck SC2016). Backticks need no escaping there. Also syncs the side-log comment to the dynamic tail_budget it actually renders. Verified: scripts suite 140/140 (was 138; the two new fixtures and the rewritten restoreClash test all fail against the pre-fix script), npm run build / typecheck / lint pass, bash -n clean. * Address review round 6: reap the kill's own orphan, tolerate the reaper * Address review: hang-bound the reaper, unblock the kill path, pin the unpinned arms - Wrap every docker call in the stale-container reap with timeout 30: an alive-but-wedged daemon blocks docker ps indefinitely, and the existing || guards only catch nonzero exits, not hangs (R3-1). - Make the kill-path container removal async in run-agent.mjs: the spawnSync blocked the event loop between SIGTERM and the 10s SIGKILL backstop for up to its 30s timeout — in exactly the wedged-daemon scenario the watchdog exists for. The main flow awaits the removal so the leak warning stays deterministic (R3-6). - Split the pre-existing gate clause for an empty CMP_R: a transient compare-API failure is "never measured", not "measured not-behind", and must not assert the branch's own code is at fault (R3-7). - Swap the timeout breaker's closing remedy to the sandbox investigation when every counted timeout was idle, mirroring the round-level split (R3-11). - Tests: pin the budget kill path separately from the idle kill path (R3-3), parameterize the idle-window parse guard over -1/0/NaN (R3-5), add a stderr-only liveness case (R3-12), pin the strict-subset A/B arm via a baseline-superset fixture knob (R3-15), and pin the breaker's current-round idle increment (R3-18). --------- Co-authored-by: verify <verify@local> Co-authored-by: qwen-code-ci-bot <qwen-code-ci@service.alibaba.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
3c3084e78a
|
fix(ci): route workflow label mutations through REST (#8761)
* fix(ci): route workflow label mutations through REST `gh pr edit` cannot mutate anything on this repository: its GraphQL lookup requests repository.pullRequest.projectCards, and with Projects (classic) attached GitHub returns the deprecation as an error, so the command exits 1 before applying the change. Reproduced from a live clone against PR #8755 — the error names the field outright. Three workflows carried label mutations through it: - pr-self-report-label.yml: every add/remove arm failed — 43 straight run failures from 2026-08-04 on; the green runs were all the nothing-to-do arm. Self-reported PRs (like #8755, whose author also opened #8750) never got the label. - qwen-autofix.yml: the `@qwen-code /takeover` and `/takeover stop` COMMAND paths never toggled the label — only the UI label events worked, so the command was dead weight wearing an ack. - repo-hygiene.yml: the add was `|| echo`-guarded, so it never failed the run — it just never labeled anything, while the fallback message blamed a label that exists. All five sites now use the REST issues/labels endpoints, which never touch that query. Two traps handled on the way: - Every label involved contains a slash, and in the DELETE the label is a PATH SEGMENT — unencoded it 404s. Encoded via jq @uri, and the tests assert the literal %2F because a real jq runs in the replay. - The REST add auto-creates a missing label, which repo-hygiene explicitly promises never to do — that site gets an existence probe first, and its misdiagnosing fallback message is corrected. Verified live on #8755 before editing anything: the exact gh pr edit call fails with the projectCards error; REST POST applies the label (backfilling the one it was owed), DELETE with %2F removes it. Tests: the stub-driven replays for both the self-report step and the takeover toggle now pin the full REST method + path (encoding included), and a repo-wide guard bans `gh pr edit --add-label/ --remove-label` in every workflow so the class cannot return. Mutation-tested, 6 of 6 caught: each of the five sites reverted to gh pr edit, and the DELETE stripped of its encoding. * fix(ci): harden REST label mutation steps per review (#8761) * fix(ci): pin REST label failure policies per review (#8761) Review round for the REST migration: - The DELETE arms tolerated EVERY failure (`|| true`), masking 403/5xx/network errors behind a green run and a false "removed" log. They now tolerate only the documented 404 race — any other failure emits a :⚠️: while keeping the step green (pr-self-report-label) and the release ack alive (qwen-autofix). - Neither replay harness could make a `gh api` call fail, so both failure policies were unpinned. They gain failure knobs (knob value on stderr like a real gh HTTP error) and now pin: 404 race silent, other DELETE failures warned, POST loud. The toggle replay also moves to -eo pipefail like the runner's bash default, reproducing the step's real failure semantics. - The jq stub enforced only the --arg shape; it now also enforces the `$l|@uri` program, so a filter mutation fails the suite instead of riding the stub's unconditional percent-encoding. - The gh-pr-edit guard misfired on comments and miscounted lines after joining continuations: comments are stripped before matching, and offenders are reported at the physical line where the (possibly wrapped) command starts. Mutation-tested with 8 probes, all caught: blanket || true on either DELETE, || true on either POST, dropped |@uri, a comment quoting the ban (stays green), an executable and a wrapped violation (both red, correct line). * Address review round 3: close the guard evasions, convert the release path Four round-3 findings, each reproduced before fixing, plus the release path the round-1 scope note deferred. - The ban guard now scans what bash executes, not the YAML surface: the decoded run: values of every parsed workflow, whole-line comments stripped, continuations joined the way bash joins them (backslash- newline removed, nothing inserted), matched whitespace-tolerantly. All three reproduced evasions — a # inside a quoted string eating the trailing backslash, wraps inside the command prefix or a flag token, and folded scalars — are fixture-pinned. Offenders report as file » job » step; line numbers stopped meaning anything after joins. - classify-release-notes.mjs mutates labels through REST now, and the guard grew an argv-form scan over .github/scripts/*.mjs that flags the old file (negative-controlled) — the release path was the last gh pr edit label site, failing silently behind continue-on-error. - JQ_STUB enforces the full invocation: -rn (with -r alone real jq evaluates zero inputs and prints nothing), the binding name l (real jq exits 3 on $l undefined), and the program. Either reproduced mutation previously expanded the substitution empty, sent the DELETE to …/labels/ with no name segment, and the 404 tolerance swallowed it. - The takeover engage POST gets the idempotent create its siblings carry, pinned to the label's real color (1D76DB): the REST add would re-create a deleted label silently with a random color. - runToggle captures writes on throw, and the engage-failure assertion now pins the ORDER its comment claims: a failing apply must leave no "takeover-ack engaged" in the captured writes — the bare toThrow passed even with the ack moved above the POST (reproduced). - The two REMOVE_ERR DELETE idioms are drift-pinned byte-identical modulo the label variable, the honest substitute for sharing shell across workflow files. Mutation-tested, 6 of 6 caught: the evadable regex restored, -rn and the binding name mutated in the workflow, the create dropped, the ack posted before the POST, and the old .mjs flagged by the new scan. --------- Co-authored-by: verify <verify@local> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
1cbf2e8fc7
|
feat(ci): auto-assign issues to area owners from labels (#8668)
* feat(ci): auto-assign issues to area owners from labels Route labelled issues to a maintainer with push access, without putting issue text in the path of a write token. Assignment is a pure function of the issue's labels and a checked-in label -> owner map, evaluated by a standalone workflow on issues:labeled. No model runs in the assignment path and the script never reads issue title, body, or comments, so untrusted issue text cannot select an assignee. Owners come from CODEOWNERS (asserted by test) and every candidate is re-checked against the collaborator permission API before the write, so editing the map cannot grant access. Among eligible owners the least loaded wins, rotating by issue number to break ties. * fix(ci): decouple issue owner map from CODEOWNERS CODEOWNERS answers who owns a code path, which is narrower than who may be assigned an issue in an area: the repository has ~44 collaborators with push access against 7 CODEOWNERS entries, so the membership test would have rejected legitimate additions such as admins and maintainers who own no path. Drop that assertion and document the actual process for adding owners. The live collaborator permission check remains the boundary. Add the validation the map does need: duplicate owners would skew load balancing, and duplicate area names would silently shadow each other under first-match-wins. * fix(ci): keep issue ownership triggers disjoint * fix(ci): recheck issue owner assignment before write * test(ci): cover issue owner label recheck * docs(ci): Correct CODEOWNERS count in issue assignment rationale * fix(automation): preserve autofix issue ownership * feat(ci): widen core issue owner pool to active repository maintainers * feat(ci): add four more collaborators to core issue owner pool * fix(ci): tighten issue-owner map validation and sync trigger docs * fix(ci): tighten issue-owner assignment tests and login validation (#8668) --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
f4802031d0
|
perf(ci): run docs-only automatic reviews at medium effort (#8648)
* perf(ci): run docs-only automatic reviews at medium effort
A 1-line docs PR costs the same 57-180 minute high-effort review as a code
change, and on a diff with zero source lines the passes medium drops - the
adversarial personas and the reverse audit - have no failure mode to hunt.
Counterfactual analysis over six dissected CI runs showed the one case
where those passes caught a real Critical was a source PR, which this gate
never touches: classification reuses the Test workflow's conservative
classify-profile.mjs (docs/**.md(x) + root prose only; markdown under any
src/ tree stays full, matching the review skill's own source rule), and
any fetch or classifier failure falls back to the full review.
Only the automatic pull_request_target review downgrades; every explicit
request (workflow_dispatch, @qwen-code /review) keeps full high effort.
Because an effective --comment forces high and medium never posts, the
downgrade drops --comment and a new step relays the review CLI's verbatim
"Review complete:" line - its machine-readable completion contract - as a
single PR comment, with a pointer for requesting the full review. The
docs-only budget is the size-aware timeout halved with a 90-minute floor.
* perf(ci): address review feedback on the docs-only medium gate
All nine review suggestions, each verified before fixing:
- review_requested is an explicit ask: the AUTO_REVIEW flag now excludes
that action (authorize write-permission-checks its requester), so a
maintainer requesting the bot's review gets the full high-effort run.
- The fetch-and-classify wrapper is extracted to
.github/scripts/ci/classify-pr-profile.sh and consumed by both ci.yml
and the review gate, so the classifier's input contract lives in one
place; distinct exit codes preserve each caller's fallback messages.
- Neither completion-line fallback mints the reserved "Review complete: "
prefix anymore, and the relayed line passes a strict not-posted
disposition allowlist - on this never-posts path any posted-form
disposition is false by definition (the measured phantom APPROVE
posted), so it falls back to a neutral non-scrapable form.
- The relay upserts by its marker (mirroring the queued-acknowledgement
step) instead of stacking a comment per push, retries the POST/PATCH
three times, and never fails the job - a failed relay after a
successful review must not trip the failure fallback into announcing
a review failure that never happened.
- The Chinese relay copy no longer parses as "发行" and renders
high-effort as 高强度 rather than 高档.
- The qwen-review docs-only-medium marker is registered in all six
BOT_COMMENT_FILTER sites in qwen-autofix.yml, so clean docs-only
relays cannot select PRs into autofix rounds as actionable feedback.
- The gate's behavioral invariants are pinned in
scripts/tests/qwen-pr-review-workflow.test.js by executing the
extracted bash: prompt-branch order (--effort medium instead of
--comment), the halve-with-90-minute-floor arithmetic, the
completion-line allowlist including the phantom shapes, AUTO_REVIEW
exclusivity, the six-site marker registration, and the shared-wrapper
routing in both workflows.
* perf(ci): harden the docs-only gate against round-2 review findings
Thirteen findings across two review passes; every fix is executed by a
test rather than asserted as text where the finding was behavioral.
- The relay marker exclusion in qwen-autofix.yml is author-scoped at all
six filter sites: only the relay bot's own marker comment is filtered,
so a human quoting the marker stays actionable feedback.
- classify-pr-profile.sh guards the 3,000-file listing cap (any mismatch
against the PR's declared changed_files classifies full), uses
mktemp+trap instead of a fixed path on the shared persistent pool, and
ships its own node:test suite (renamed source→docs pins the projection
contract; exit codes 2/3 pinned) registered in HELPER_TESTS.
- classify-profile.mjs restricts reserved root prose basenames to inert
extensions - README.js / SECURITY.ts / LICENSE.sh classify full.
- The completion-line allowlist binds to pr-<number> and to the only
verdict a medium run can produce (Comment, not posted) - a stale line
for another PR or an Approve-shaped injection falls back to neutral.
- A dedicated review_completed output gates the relay: the state/head
guards exit 0 without running the review, and outcome==success alone
would have announced a review that never ran.
- The relay upsert filters by the authenticated bot login, re-resolves
the comment id on every attempt, and falls back to POST when the PATCH
target is gone - a participant posting the marker can no longer capture
the upsert, a transient listing failure no longer mints duplicates.
- The gate and relay are now executed under stubbed executables in
qwen-pr-review-workflow.test.js (docs_only/full/failure/explicit
scenarios; POST/PATCH/never-fail branches), the AUTO_REVIEW pin covers
both guard halves, and the marker contract is pinned producer-side and
filter-side.
* perf(ci): fix the medium Request-changes swallow and the stale docs badge
Round-3 review findings (2 Critical, 8 test-gap Suggestions), each fix
executed by a test where the finding was behavioral:
- The completion-line allowlist accepts `Request changes, not posted` -
compose-review caps only Approve at medium, so a docs-only run that
verifies a Critical legitimately emits Request changes, and the old
Comment-only allowlist swallowed exactly the blocker-finding outcome
into the neutral fallback. Target binding to pr-<number> is unchanged
and now pinned by a test, as is the last-line selection over a stale or
injected earlier completion line.
- A stale docs-only badge can no longer outlive its revision: the full
automatic review path now supersedes the bot-authored marker comment
(strikethrough + superseded note) via a new --update-only mode that
never mints a badge where none existed.
- The marker+author upsert protocol is extracted to
.github/scripts/upsert-bot-comment.sh - one implementation shared by
the relay and the supersede step (the per-step copies had already
drifted), with its own node:test suite covering the author scope, the
per-attempt re-resolution (deleted-mid-retry falls back to POST), and
the --update-only no-op; registered in HELPER_TESTS.
- The classify-pr-profile gh stub now applies the wrapper's own --jq
argument with real jq over API-shaped fixtures, so the projection
contract is genuinely under test (negative control: dropping `status`
turns the renamed-source scenario red).
- New pins: review_completed wiring end to end (run-step emit + both
consumers' if clauses), the auto_review output->env wiring at both
links, and both AUTO_REVIEW guard halves.
* perf(ci): never let a failed lookup mint or keep a stale docs badge
Round-4 review findings (1 Critical, 7 Suggestions):
- The upsert script no longer conflates failed lookups with empty
results: the authenticated login, the listing, and the jq extraction
are all resolved inside the retry loop as one prerequisite chain, an
attempt whose prerequisites failed retries instead of falling through
to POST (the shape that minted a permanent duplicate badge off one
transient 5xx), and --update-only exits 1 on a failed lookup so the
supersede warning fires instead of a false no-op success. New tests
pin the failed-listing-then-PATCH path, the persistent identity
failure, the update-only failure exit, and the update-only PATCH.
- Supersede now covers every path that owes the correction: a FAILED
full review and an EXPLICIT requested review (the badge's own CTA)
both retire the badge, gated only on docs_only_medium == 'false' -
empty on runs that failed before classifying, so a badge is never
superseded on ignorance. The body is cause-neutral: it asserts only
that the badge described an earlier revision.
- The marker literal is defined once per step (MARKER variable, the
qwen-triage convention) and shared between body and lookup argument;
a pin requires the definition and --update-only on the supersede
invocation.
- New behavioral pins: the Approve verdict stays rejected by the
allowlist, github_ci_only never downgrades (CI helpers are
executable), and review_completed's emit position is asserted AFTER
the closed-PR and stale-head guards (the hoist mutant survived
position-independent contains checks).
* perf(ci): make docs_only_medium three-valued and pin the untested guards
Round-5 review findings (1 Critical, 6 Suggestions):
- docs_only_medium no longer conflates "determined not docs-only" with
"never determined": the output is three-valued ('' when the
classification failed or never ran), so a transient classifier failure
or a dispatch dry-run can no longer retire a still-accurate badge. The
supersede condition names its two licensed paths explicitly - a
POSITIVE not-docs-only determination (without requiring review
success), or an explicit comment-mode review that completed (the
badge's CTA; report-mode dry runs retire nothing).
- The count-mismatch fallback in classify-pr-profile.sh logs to stderr,
so a systematic divergence is distinguishable from every PR genuinely
classifying full.
- Six probed surviving mutants now each turn a test red: the supersede
body is executed (existing-badge PATCH and the never-fail guard),
ci.yml's rc-handling fragment is executed (exit 0/2/3 with the
full fallback), the changed_files fetch failure exits 2, duplicate
badges PATCH the last (newest) comment, and the relay's POSTed body
must carry the marker that keys both the upsert and the supersede.
* perf(ci): bind the docs badge to the reviewed head and retire it on failure
MDX pages are executable (imported components, expressions), so the
classifier no longer treats them as inert docs-only changes. The relay
and supersede writes re-read the live PR state/head immediately before
the mutation and skip unless the PR is still open at the reviewed SHA,
the badge body names that SHA, a failed docs-only review now retires the
singleton badge instead of leaving the previous revision's outcome
visible, and the retired wording is cause-neutral (an explicit review
can complete on the very head the badge describes).
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
---------
Co-authored-by: verify <verify@local>
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com>
|
||
|
|
5fdcdb28e2
|
fix(ci): avoid root-owned npm cache workspace files (#8669) | ||
|
|
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. |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
6097d7ab63
|
ci: auto-minimize comments from org-blocked users (#7899)
* ci: auto-minimize comments from org-blocked users Adds a scheduled workflow that runs every hour to scan recent issue/PR comments and minimize any from users blocked at the org level. This cleans up spam comments that were posted before a block was applied. The workflow: 1. Fetches the org's blocked-user list via REST API 2. Queries recent comments (last 2h) via GraphQL 3. Matches comment authors against the blocked list 4. Minimizes unmatched comments as OFF_TOPIC via GraphQL Also triggerable manually via workflow_dispatch with a configurable lookback window. * ci: use repo blocklist file instead of org blocked-users API The org blocked-users API requires admin:org scope which the CI bot PAT doesn't have. Switch to a plain-text blocklist file at .github/spam-blocklist.txt — one username per line, case-insensitive, # for comments. No special scopes needed. Also adds danialzivehdadr as the first entry. * ci: make auto-minimize-spam failures visible, handle empty blocklist * fix(ci): address review feedback on auto-minimize-spam workflow (#7899) - Change hours input type from string to number (project convention) - Extract duplicated step-summary writes into write_summary() function - Capture gh stderr (2>&1) and include it in failure warnings - Document coverage limits in header comment * fix(ci): quote auto-minimize-spam expressions to pass yamllint (#7899) * test(ci): add regression guards for auto-minimize-spam workflow invariants (#7899) --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> |
||
|
|
1b5c36ce15
|
ci: add isolated DSW SWE-bench release pipeline (#7656)
* ci: add isolated DSW SWE release pipeline
* ci: bootstrap branch-only DSW full-suite test
* Revert "ci: bootstrap branch-only DSW full-suite test"
This reverts commit
|
||
|
|
15ca9818e0
|
fix(ci): restore verify workspace permissions (#7992) | ||
|
|
43c35e533b
|
fix(ci): keep the post-merge E2E signal on main alive (#7795)
* fix(ci): stop cancelling in-progress E2E runs on main Merges land on main roughly every 18 minutes (median) while a full E2E run takes about 40, so cancelling the in-flight run on every push starved the suite: across the last 100 push runs, 67 were cancelled and only 25 ever reported a result. The nightly regression was the only reliable signal, and each cancelled run still burned roughly 10 minutes on three runners before dying. Turning cancellation off for main hands the coalescing to GitHub's concurrency queue, which keeps at most one pending run per group and cancels the previously pending one. The queue therefore collapses to the newest tree on its own while the in-flight run always finishes, so every result covers the batch of commits merged since the last one — bisect that range when it goes red. Dev branches keep cancelling superseded runs, where only the latest push matters. * fix(ci): dedupe main CI failure issues by failing test The autofix issue for a red main was deduped on the commit SHA, so a standing failure opened a brand new issue on every merge: one broken E2E test on 2026-07-26 produced six duplicate issues in twelve hours, each pointing the autofix agent at an unrelated commit. The failing tests are now identified from the logs of the failed jobs and used as the dedupe key, with one marker per test so a failure set that grows still matches the issue that already tracks part of it. Later commits hitting the same failure are appended to that issue as recurrences (bounded, newest first) instead of opening another one, and notes written by a human or the agent are preserved when the machine-owned trailer is refreshed. Runs with no identifiable test — an install or build break — keep the previous per-commit behaviour. * fix(ci): keep the failure analysis out of the bot-PAT job Identifying the failing tests means running a helper from the repository, and the workflow deliberately checked out nothing so that the job holding the bot PAT could never execute repository code — an invariant its own test enforces. Rather than weaken it, the work is split: a read-only job checks out the tree, reads the failed run's logs, finds any issue that already tracks the failure and renders the title and body, handing both over as outputs. The job with the PAT keeps checking out nothing and only writes what it was given. The invariant test now asserts that separation — the PAT job runs no repository code and holds only issue write — instead of banning checkout everywhere in the file. * fix(ci): harden main CI failure dedupe per review (#7795) - Match the `[run <id>]` link text instead of the run URL when deduping recurrences: `/301` is a substring of `/3010`, so the URL match silently deleted an unrelated run's line. - Rebuild the machine-owned "## Also failing" section from the live failure set on every merge so a test that has since been fixed drops out instead of being listed forever. - Assert the analyze checkout is SHA-pinned with persist-credentials disabled and pin the failed-job log-download paths in the workflow test. - Add an e2e workflow test guarding the cancel-in-progress expression that keeps in-progress runs on main from being cancelled. * fix(ci): bound the issue body and randomize the heredoc delimiter (#7795) * fix(ci): test the runCli --existing merge path (#7795) * fix(ci): address review feedback on e2e signal PR (#7795) - Assert the full cancel-in-progress expression including && so a mutation to || is caught by the e2e-workflow test - Filter the capped-summary line from missingTests so it is not rendered as a fake bullet under Also failing --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: Qwen Code Bot <qwen-code-bot@users.noreply.github.com> |
||
|
|
45a6a69cf0
|
feat(triage): add revert-pattern high-risk path detection (#7414)
* feat(triage): add revert-pattern high-risk path detection Replace the behavior-neutral PR filter (PR #7414 v1, ~2% hit rate) with a data-backed triage gate based on revert-history analysis of 111 revert commits and 46 unique reverted PRs in this repo. Stage 1e checks three signals identified by the analysis: - touches_high_risk (66.7% precision, 32.3% recall) - contested-merge pattern (50.0% precision, 19.4% recall) - non-maintainer + high-risk (58.3% precision, 22.6% recall) The gate escalates review depth and recommends maintainer sign-off; it never blocks or closes PRs. Design doc and analysis scripts included. * fix(triage): avoid stale-exempt hold label * fix(triage): address review risk detection feedback * fix(triage): tighten high-risk path patterns * fix(triage): address review feedback on Stage 1e revert-pattern gate (#7414) * fix(triage): address round-2 review feedback on Stage 1e gate (#7414) - Fix APPROVE → APPROVED state name (GitHub API enum) - Use gh api --paginate for file listing (fixes 100-file truncation) - Anchor shell/relaunch/sandbox patterns with (^|/) to avoid false positives - Append || true to grep (exit 1 on no match is the 92% case) - Scope E2E recommendation to write-access authors per Stage 2c - Add bot author filter to contested-merge query - Define core paths explicitly in contested-merge condition - Wire Stage 1e do-not-auto-approve into Stage 3 guardrail - Replace precision percentages with p-values/raw counts in skill text - Add sampling caveat and statistical significance notes to design doc - Fix design doc errors: 71%→61.5%, 10→8 PRs, Rule 3 attribution, ~20% baseline→10% prevalence, Area field, no_e2e inconsistency - Make test assertions specific to Stage 1e (not vacuous) - Add Risk: template field assertion - Revert drive-by prettier reflow - Note need-discussion label removal by maintainer * fix(triage): address round-3 review feedback on Stage 1e gate (#7414) - Separate gh api call from grep so API failures are visible instead of being masked by || true (rc:3660753982) - Include author identity in contested-merge jq output and require different reviewers for the disagreement check, avoiding false positives from same-reviewer iteration (rc:3660753990) - Add Stage 1e to the approval summary checklist so it is not omitted from the pre-approval conditions (rc:3660753994) * fix(triage): address round-4 review feedback on Stage 1e gate (#7414) * fix(triage): use portable ERE grep for test-file exclusion (#7414) * fix(triage): guard deferred approval on discussion label * fix(triage): keep only supported revert signal --------- Co-authored-by: qwen-code-bot <qwen-code-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> |
||
|
|
c994524a2d
|
feat(autofix): retry deterministic rejection once (#7796)
* feat(autofix): retry deterministic rejection once * fix(autofix): use repaired verification result * fix(autofix): preserve repair safety context * test(autofix): cover empty verification outcomes |
||
|
|
2da42099d4
|
fix(ci): don't fail triage cleanup when there is nothing to clean (#7688)
The 'Clean stale agent state' step strips non-allowlisted keys from the persistent workspace's local git config through a `git config --list | grep -ivE <allowlist> | while ...` pipeline. When the config holds only allowlisted keys — the steady state on a reused runner this step already sanitized, since actions/checkout's post step removes its auth extraheader at job end — grep matches nothing and exits 1. Under the default `bash -e` shell combined with the script's `set -o pipefail`, that kills the step exactly when there is nothing to clean, before any output, and every downstream triage step is skipped (seen on run 30095456731, runner ecs-qwen-runner-sg-4). Guard the grep with `|| true` so an empty match feeds an empty loop instead of failing the job. Sanitization behavior is unchanged: non-allowlisted keys (core.pager, include.path, `!`-aliases) are still stripped. The workflow test harness missed this because its allowlist test re-assembles the pipeline without the step's shell flags and always plants non-allowlisted keys first. Add a steady-state regression test that runs the step's actual script under `bash -e` against a config holding only allowlisted keys, plus a negative control that strips the guard and demands the step die — red on the old workflow, green now. Co-authored-by: verify <verify@local> |
||
|
|
b1ce0c2087
|
refactor(autofix): extract review verification runner (#7644)
* refactor(autofix): extract review verification runner * test(ci): follow extracted autofix verifier * docs(autofix): document the review verification runner env contract (#7644) |
||
|
|
f8014652a5
|
test(triage): regression-guard the triage workflow, and make the git cleanup an allowlist (#7660)
Guards the security-critical invariants of qwen-triage.yml that broke silently once already — the `settings_json:` input name was wrong, so the action dropped it and the review agent ran with the full default toolset and no deny list. A new `node:test` suite (wired into the shared HELPER_TESTS list both CI paths run) asserts: the `settings:` input name (never `settings_json:`), the tools.core registration whitelist and the deny list, the fork-PR runner routing invariants, and the git exec-vector cleanup. It also flips that cleanup from a best-effort denylist — which kept missing new families (pager, filter.*, includeIf subsections, url.*, credential…) — to a keep-known-safe allowlist: unset every local config key that isn't plumbing actions/checkout needs (repo format, remote, branch, fetch/gc/pack/index, safe.directory, extensions, submodule url/active/branch — not submodule.*.update, which can be `!cmd`). This closes the whole exec-vector class, including knobs not yet enumerated. The harness runs the workflow's actual allowlist pattern against a scratch repo to prove it unsets every exec family and preserves the checkout plumbing. Co-authored-by: verify <verify@local> |
||
|
|
cb98102149
|
ci(autofix): add cross-package contract verification (#7642) | ||
|
|
14f1f2bb36
|
fix(ci): don't let one failing scenario sink the whole visual preview (#7511)
The web-shell visuals render runs every screenshot and flow in a single `test:e2e:visuals`, and that step had no `continue-on-error`, while the compose and upload steps had no `if: always()`. So one failing or timing-out scenario failed the job, the artifact was never uploaded, and the publish workflow had nothing to post — the entire preview vanished even when every other scenario passed and its PNG was already on disk. A flow (a long multi-click sequence) is the most fragile scenario kind, so the fragile one silently takes down the deterministic screenshots. PR #7498 hit exactly this: 29 scenarios passed, one new channel-management flow timed out, and the PR got no preview and no comment at all. Make the after-capture step `continue-on-error` so the passing captures survive and the later steps still compose and upload them. The publish job only runs on a `success` conclusion, so the job must stay green — but a masked failure must not read as a clean preview. Ship the step's real `.outcome` (which continue-on-error does NOT mask, unlike `.conclusion`) to the publisher as `render-status.txt`, and have the comment builder use it: an empty preview whose render failed says "one or more scenarios failed to render" and is explicitly NOT the reassuring green check or the coverage-gap prompt (both imply the render ran); a partial preview is labelled partial above the shots that did render. A missing status file (older run) defaults to complete, so this only ever adds a warning, never suppresses a real preview. The failing scenario still needs fixing — it's now surfaced in the comment rather than by silently deleting everyone else's preview. Co-authored-by: wenshao <wenshao@example.com> |
||
|
|
82157d1c03
|
fix(ci): tell a visuals coverage gap apart from "no visual change" (#7375)
An empty visual preview means one of two opposite things: the change genuinely moves no pixel, or no scenario renders the UI it touches. The bot printed the same green check for both, so the second — a coverage gap, where the preview literally cannot see the feature — read as a clean bill of health. That has now happened three times (#7035 primary label, #7221 worktree badge, #7365 empty-state toggle), each caught only because a maintainer noticed the missing image and asked. The signal to tell them apart was already there and unused: the render workflow only runs when the web-shell client or webui source changed, so an empty preview is by construction "UI code changed, nothing rendered differently". When no view changed, look at which files the PR touched. If any are render-shaping (.tsx / .css / .svg under the rendered surface, excluding test and scenario code), list them and say the result is ambiguous, with a pointer to where a scenario goes. Otherwise keep the green check — a logic-only PR with no visual delta is expected, and prompting there would train everyone to ignore the prompt when it matters. The path list comes from the PR files API in the privileged publish job, which never checks out PR code; if that call fails the comment falls back to the current wording. Co-authored-by: wenshao <wenshao@example.com> |
||
|
|
cda0e0348d
|
ci: move release-note classifier from per-PR workflow to release-time batch (#7339)
* ci: move release-note classifier from per-PR workflow to release-time batch * fix(ci): enumerate release PRs from generated notes * fix(ci): reconcile release note labels * fix(ci): address release classifier review * fix(ci): surface release classifier failures |
||
|
|
eca654f365
|
fix(autofix): resolve owning package for nested paths; report verify-failed handoffs as not pushed (#7330)
* fix(autofix): resolve owning package for nested paths; report verify-failed handoffs as not pushed The verify gate mapped each changed file to a flat `packages/<dir>` and read `<dir>/package.json`, which ENOENT-crashed on nested packages such as packages/channels/base — the container packages/channels has no package.json. Walk each changed file up to its nearest package.json in both the issue-fix and review-address verify steps, and skip any candidate that still has none. When such a verify failure follows an agent commit, the review-address handoff rendered the agent's optimistic address-summary.md (which can cite a commit SHA) under a neutral "what I found" heading, so a maintainer chased a commit that was discarded with the runner workspace. An EXIT trap now records any post-commit non-zero exit as outcome=failed, and the handoff states plainly that the change did NOT pass the gate and was NOT pushed. Tests: walk-up detection over a nested package tree, the outcome=failed trap, and the not-pushed handoff wording — each mutation-verified. * refactor(autofix): extract owning-package resolver to a shared staged script Addresses review on #7330. Extract the changed-file → owning-package walk into .github/scripts/resolve-owning-packages.sh, staged to RUNNER_TEMP from the trusted base alongside check-settings-schema.sh and invoked from both verify gates, so the two gates cannot drift into resolving packages differently (the 8-line walk was otherwise duplicated verbatim in each). Updates the package-scripts test that pinned the old inline grep. Narrow the verify-failed handoff lead-in to "This change was NOT pushed": four paths set outcome=failed BEFORE the deterministic gate runs (agent abort via failure.md, dirty tree, unchanged branch, missing address-summary.md), so the previous "did NOT pass the verification gate" claim was factually wrong for them. The specific reason stays in the headline and the quoted summary. * style(autofix): brace variable references in resolve-owning-packages.sh The repo's shellcheck gate runs --enable=all --severity=style, under which bare $f/$d references trip SC2250 (prefer ${var}). Brace them to match the convention already used in check-settings-schema.sh, and update the script content assertions accordingly. Verified with shellcheck 0.11.0 using the exact CI flags: clean. * fix(autofix): resolve owning workspace via npm query; key unpushed-handoff on commit existence Addresses the deeper review on #7330. Blocking issue: the "nearest package.json" resolver mapped a change under a workspace's fixture/example package (e.g. packages/cli/src/commands/extensions/examples/starter) to that fixture, whose test script is not Vitest — silently SKIPPING packages/cli's own tests, a coverage regression invisible in the log. Resolve against the authoritative `npm query .workspace` set instead and take each file's longest-prefix workspace: nested workspaces (packages/channels/base) match exactly, fixtures and non-workspace paths (packages/sdk-python, packages/README.md, the excluded packages/desktop) drop. Also harden the resolver against a final line with no trailing newline and against an unmatched last line, which under `set -o pipefail` would otherwise abort the script. Handoff wording: keying "was NOT pushed / commit discarded" on outcome=failed was wrong for the abort paths (failure.md, dirty tree, unchanged branch, missing address-summary.md), which set outcome=failed before ever making a commit. Record committed=true right after checkout — before any gate can fail — and key the wording on that; the abort/no-op paths keep the neutral framing. This removes the EXIT trap entirely (its only observable effect was that wording), so it no longer mislabels pre-commit failures either. * fix(autofix): expand workspaces on-disk so branch-added packages are tested; harden resolver Addresses the re-review on #7330. The resolver sourced its workspace set from `npm query .workspace`, which reads node_modules — installed from the BASE checkout. A workspace the PR branch ADDS (a new channel adapter, a new sdk — the issue-fix job's whole purpose) was invisible, so its tests were silently skipped, and for a nested new package the ENOENT crash this PR fixes turned into a silent skip. Expand the set from the on-disk root package.json `workspaces` globs instead (shallow `dir/*` + literals, honouring `!` negations, keeping dirs with a package.json): it reflects the branch, matches what `npm run --workspace` accepts downstream, and needs no install. Verified to reproduce `npm query`'s set exactly on the current tree. Also from the review: - Fail the gate loudly on an empty/unreadable workspace set instead of the silent "no package changes" skip, and drop the now-unneeded `|| true` at both resolver call sites (the resolver already exits 0 on legitimate no-match). - Record committed=true at the TOP of the step (ref-only diff), covering an agent that commits then aborts, and count only `git diff --quiet` exit 1 as a commit (128 is a git error, not a discarded commit). - Correct the two call-site comments that still described the superseded nearest-package.json approach. Also hardens the resolver against a final changed-path with no trailing newline and an unmatched last line under `set -o pipefail`. --------- Co-authored-by: wenshao <wenshao@example.com> |
||
|
|
8fe1f926e9
|
ci: auto-skip internal CI changes in release notes (#7251)
* ci: auto-skip internal CI changes in release notes * ci: harden release note classifier * fix(ci): close release note classifier review gaps * fix(ci): close release classifier review gaps * fix(ci): avoid release classifier label loop * test(ci): cover release classifier test path variants |
||
|
|
076427650d
|
feat(ci): auto-open a deflake fix issue for confirmed flaky tests (#7231)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
* feat(ci): auto-open a deflake fix issue for confirmed flaky tests
The CI Failure Patrol reruns flaky failures but never fixes them, so
the same tests flake forever on a rerun treadmill. This closes the
loop: when the patrol classifies a rerun as a nondeterministic TEST
(not infra), it now also opens ONE deflake issue that the existing
autofix issue pipeline develops into a reviewable stabilization PR.
- ci-flaky-patrol SKILL: a rerun decision whose cause is a specific
named flaky test carries an optional flakyTest {file, name}; infra
reruns (ENOSPC, network, runner death) never do.
- ci-flaky-rerun.mjs: validates flakyTest (malformed → the whole
decision is rejected, so a bad classification can't open a bogus
issue); after a rerun, ensureDeflakeIssue upserts a deflake issue
deduped by a stable (file, name) marker — one open issue per flaky
test across all PRs — labeled status/ready-for-agent + autofix/
approved so the scheduled autofix scan picks it up.
- .qwen/skills/deflake/SKILL.md: constrains the fix to four
assertion-preserving patterns (raise timeout/poll budget, stabilize
timing/waiting, make randomness/time deterministic, isolate
interference) and forbids skipping/deleting/loosening the check;
write failure.md if none applies or the failure looks like a real
bug. The produced PR is reviewable, never auto-merged.
Tests: deflakeKey stability/collision-freedom, the bilingual issue
body, one-issue-per-test dedup, no issue for infra reruns, and
malformed-flakyTest rejection. 34/34 across both patrol suites.
* fix(ci): deflake review hardening — rerun survives bad metadata, no markup injection
Addresses the two Criticals + suggestions on #7231:
- **Critical: a malformed/over-length flakyTest no longer kills the
rerun.** flakyTest validation is removed from validDecision (which
gated the PRIMARY action on secondary metadata — a >200-char nested
test name or a null silently dropped a valid rerun). Well-formedness
is now checked in ensureDeflakeIssue, which simply skips the deflake
issue when the metadata is bad; the rerun always stands.
- **Critical: markup/mention injection via the test path/name.** file
and name are code-span-stripped of backticks (which cannot be escaped
inside a span and would break out into live Markdown, turning
into a mention in a bot-created issue) and both now sit in
code spans. safeReason alone did not close this (it does not touch
backticks).
- Best-effort deflake: ensureDeflakeIssue is wrapped in try/catch so a
transient createIssue failure — after the marker is already posted —
no longer surfaces as a misleading "skipping PR" and permanently
suppresses the deflake; it retries on the next flaky occurrence.
- Run link uses the patrol's own repo (client.repo) instead of the dead
target.repo, so deflake issues on a fork don't 404.
- Body reworded: it no longer claims the rerun already passed (it runs
right after the rerun is triggered) — it says a real deterministic
failure is NOT flakiness and must not be stabilized.
- SKILL: bound file/name to 200 chars, and note a malformed one is
ignored (never drops the rerun).
Tests: malformed flakyTest keeps rerun (no createIssue); long title
truncates ≤240; backtick path/name cannot inject; run link honors the
repo; a throwing createIssue leaves the rerun intact. 38/38.
---------
Co-authored-by: wenshao <wenshao@example.com>
|
||
|
|
a319dd0b86
|
ci(web-shell): denoise cross-job font-AA so visual previews stop false-flagging (#7210)
The before/after preview flagged text-heavy views (e.g. workspace-sidebar) as ~0.1-0.3% "changed" on PRs that do not touch them — #7204 is a live example (the two panels are pixel-for-pixel indistinguishable). Root cause: base and head render in SEPARATE CI jobs, so Linux font anti-aliasing is not bit-identical between them, and the naive per-pixel diff counts the scatter of isolated / 1px-wide glyph-edge pixels that leaves. At the tight 0.02% threshold that scatter crosses the line. Measure the changed fraction AFTER a cluster denoise: a differing pixel counts only when at least 4 of its 8 neighbours also differ. AA scatter (isolated = 0 neighbours, a 1px line = 2) erodes to ~zero, while a real change — a badge, chip, icon, panel — is a solid block whose interior keeps 5-8 and easily clears the threshold, so the threshold stays tight without raising it (which would miss small real changes like a workspace badge). The browser now returns a compact bit-mask; the denoise + count run in node against the unit-tested countDenoisedChanges, so there is one tested implementation of the metric. Co-authored-by: wenshao <wenshao@example.com> |
||
|
|
5a35435df1
|
ci(autofix): recover from generated-artifact CI gates and stop silent stalls (#6998)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none (push) Waiting to run
E2E Tests / E2E Test - macOS (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
* ci(autofix): teach the review loop about generated-artifact gates and stop silent stalls
The autofix bot stalled on PRs that edit settingsSchema.ts without
regenerating settings.schema.json (e.g. #6984): a CI-only freshness gate
it could neither run, see, nor recover from.
- Give the agent the tool and the instruction to regenerate: add
`npm run generate:settings-schema` to the develop-issue and address-review
coreTools allowlists, and a SKILL rule to regenerate + commit a source's
generated artifact.
- Mirror CI's "Check settings schema is up-to-date" step in both verify
gates so a stale artifact fails locally instead of red-on-CI after push.
- Inject the actual failing STEP name + a log excerpt into feedback.md so
the agent diagnoses from the real failure instead of guessing from local
test runs. SKILL now forbids "pre-existing"/environment excuses without
evidence.
- Decouple the feedback watermark from base-sync pushes: use the last eval
marker (what the agent evaluated), not the head commit date, so an
"Update branch" merge can no longer bury unaddressed maintainer feedback.
Use PR createdAt as the pre-first-eval floor.
- Bound the pending-check skip so a check wedged pending can't strand a PR
forever; always post a handoff comment + eval marker on failure so the
loop never goes silent; add an issue_comment trigger so an @-mention from
a trusted maintainer re-triggers the review pass promptly.
* ci(autofix): drop comment-trigger and raw-log injection; keep them within existing safety guards
Respects two deliberate, tested design decisions that the first cut collided
with (caught by scripts/tests/qwen-autofix-workflow.test.js):
- Drop the issue_comment @-mention trigger and its route branch: the workflow
intentionally does not expose comment-triggered autofix (only
pull_request_review:submitted) to avoid redundant runs and comment-command
surface. The scheduled scan plus the watermark fix already re-target a PR
after maintainer feedback.
- Drop the raw CI-log injection into feedback.md: feedback fed to the model is
deliberately sanitized and must not pull in URLs / raw context (a
prompt-injection surface). Keep only the sanitized check-name rendering
(.name // .workflowName, still gsub+truncated).
Update the assertions that the retained improvements (watermark decoupling,
pending-check staleness bound, always-post-handoff-on-failure) legitimately
changed. Workflow test: 48/48 green.
* ci(autofix): address review — handle cancellation, fold a PR fetch, cover schema commands
Addresses the three inline /review suggestions on the PR:
- Handoff on any non-success end, not just "failure". A 120-minute job-timeout
cancellation sets job.status = "cancelled", which the "== failure" check
missed — leaving no marker and no comment, so the next scan re-targeted the
same feedback with the same round (an invisible loop). Use "!= success";
the step only runs on failure()/cancelled()/dry-run and dry-run is excluded.
- Fold the createdAt fetch into the existing statusCheckRollup gh pr view call
(statusCheckRollup,createdAt), removing one GitHub API round-trip per PR
scanned.
- Add test coverage the earlier diff lacked: assert generate:settings-schema is
in both agent allowlists and that both verify gates run the schema-freshness
check, so a future edit can't silently drop the guard this PR adds.
* ci(autofix): fix handoff/staleness design flaws from review (4 critical + 2 suggestions)
Addresses the CHANGES_REQUESTED review of the handoff (E-4) and pending-check
staleness (E-3) logic:
- Suppress the handoff once a run published a result (OUTCOME fixed/noop), so a
later always() step failing the job (e.g. artifact upload) can no longer post
a contradictory acted=false handoff over a reported success.
- Bound the agent step at 80m, well under the 120m job timeout, so a runaway
agent fails the STEP (not the job) and the always() report step still runs and
hands off — a job-level timeout would cancel that step too and go silent.
- On a pre-prepare crash (empty NEWEST) the watermark can't advance, so write a
terminal marker (round = MAX_ROUNDS) and skip on the highest marker round
(not last-by-ts), so the scan stops re-handing-off instead of repeating until
MAX_ROUNDS.
- Raise the pending-staleness bound from 30m to 240m so an active check
(review-pr ~50m, review-address up to 120m) is never aged out mid-flight and
the same feedback double-processed; only truly-dead checks are ignored.
- Prefer the agent's detailed failure.md over the generic handoff.md wrapper.
Adds a bash-replay test that extracts the actual POST_HANDOFF decision and
MARK_ROUND logic from the workflow and exercises the state transitions
(published+late-failure, dry-run, verify failure, pre-verify crash,
cancellation; terminal vs incremental round). Workflow test: 49/49.
* ci(autofix): address review nits — symmetric schema-gate outcome, softer SKILL wording
Two non-blocking review suggestions:
- The issue-phase verify gate's schema-freshness check now writes
outcome=failed before exit 1, matching the address-review gate, so the
issue-phase step summary shows outcome=failed instead of outcome=unknown.
- Reword the SKILL rule from "do not invent environment excuses" to "do not
skip a failing check by attributing it to the environment without evidence,"
which keeps the intent (no hand-waving a real failure as an env issue)
without discouraging the agent from reporting a genuine infra failure.
* ci(autofix): close review edge cases — structural schema gate, immutable floor, robust handoff
Review round 3 (2 critical + 3 suggestions):
- Run the review gate's settings-schema freshness check BEFORE the no-op/
unchanged return, so a stale-schema PR the agent wrongly no-ops fails
(outcome=failed) instead of being reported as evaluated while CI stays red —
the exact motivating bug. Single check now covers every path; ordering is
asserted in the test.
- Never fall back to the mutable head commit date for the pre-first-eval
watermark floor: if the PR metadata query fails, use an empty (over-inclusive,
never-buries) floor. A base-sync HEAD as the floor would recreate the burial
bug. Removes the now-unused HEAD_SHA lookup.
- Guard the terminal handoff marker's timestamp (MARK_TS=${NEWEST:-${WATERMARK:-unknown}})
so a cascading API failure that blanks WATERMARK can't emit an unparseable
`ts=` that the scan regex skips, defeating the terminal-round guard.
- Truncate failure.md through `iconv -f utf-8 -t utf-8 -c` so a byte-level
head -c can't split a multi-byte sequence and corrupt the comment body.
- A pre-prepare crash (empty NEWEST) now says "could not start evaluation"
instead of "round 5/5", which would imply MAX_ROUNDS attempts were made.
Workflow test: 49/49.
* test(autofix): assert regression-catching invariants flagged in review
Four review suggestions, test-only: assert the else-branch floor
(EFF_WM=${CREATED_WM}, not the old PUSH_WM), the staleness jq filter
(.startedAt // ... // $cut), the JOB_STATUS env declaration (else it is always
empty → over-eager handoffs), and the .name // .workflowName feedback format —
so a regression on any of these is caught rather than passing silently.
* ci(autofix): fix iconv silent-abort, fold branch fetch, tighten staleness filter
Review round with a real regression in my own round-3 UTF-8 fix:
- CRITICAL: `iconv -c` exits 1 whenever it discards a byte split by `head -c`,
and under the step's `set -eo pipefail` that aborts before the eval marker +
gh pr comment run — a silent stall, the exact failure this block prevents.
Add `|| true`; the cleaned text is already emitted, so the handoff continues.
- Fold headRefName into the PR_META fetch (headRefName,statusCheckRollup,
createdAt) and derive BRANCH from it — one fewer API call per scanned PR.
- Simplify the pending-staleness clock to `.startedAt // $cut`: statusCheckRollup
has no updatedAt and pending checks have no completedAt, so those fallbacks
were dead and contradicted the comment. Now a check blocks only if it actually
started within the bound; comment matches the code.
- Tests: assert `.startedAt // $cut) > $cut` (the comparison, not just the
constant) and the `|| true` guard, so a flipped comparison or a dropped guard
is caught. 49/49.
* ci(autofix): review round — skip empty branch, hoist staleness vars, robust sentinel
Six review suggestions (no criticals):
- Skip a candidate PR when the metadata fetch fails (empty branch) instead of
falling through to an address job that fails on `git checkout -B "" origin/`
and posts a misleading handoff. This also means CREATED_WM is only reached
with populated metadata (subsumes the empty-floor warning suggestion).
- Hoist the invariant PENDING_STALE_MIN / PENDING_CUTOFF out of the per-PR loop
(one `date` fork instead of one per candidate).
- Replace the MARK_TS "unknown" sentinel with a far-future ISO-8601 date, so it
is non-empty AND sorts above real timestamps without relying on an
undocumented lexicographic quirk of a bare word.
- Cross-reference comment on the positional eval-marker regex noting it must
stay in lockstep with every write site (ts= acted= round=).
- Tests: document OUTCOME="" + JOB_STATUS=success → no handoff, and assert the
empty-branch skip guard.
* ci(autofix): DRY schema check via --check, widen handoff detail, honest terminal recovery
Three review suggestions:
- Replace both duplicated schema-freshness blocks with the generator's in-process
`--check` mode (verified: exit 1 when stale, 0 when fresh; no disk write, so
the review gate's later no-op git-diff is unaffected). Single source of truth
with CI; a future change to the check lives in one place.
- Widen the handoff DETAIL_FILE search to address-summary.md/no-action.md: when
the agent succeeds but a post-agent verify gate fails (e.g. the schema gate),
OUTCOME=failed with only the success outputs present, and "Push and report" is
skipped — so this was posting a false "crashed or timed out" and dropping the
agent's real summary.
- Correct the terminal-crash headline: the marker makes the scan skip forever
(even forced dispatch), so "re-trigger if transient" was misleading; the
headline now states the real recovery — delete the terminal autofix-eval
marker comment, then re-trigger. (Keeps the terminal design a prior review
asked for; only the advertised recovery is fixed.)
* ci(autofix): revert schema gate off --check (removed from main by #7031); jq replay
- CRITICAL: `--check` was reverted from main's generator by #7031 (
|
||
|
|
ddaf3aa548
|
ci(web-shell): before/after visual previews, showing only changed views (#6963)
* ci(web-shell): before/after visual previews, showing only changed views The visual-preview bot posted the same fixed set of canned screenshots on every web-shell PR, so it could not show what a PR actually changed (a mermaid/split feature was invisible) and added noise on PRs that touch the UI only trivially. Render each scenario against BOTH the PR base (`main`) and the PR head, pixel-diff them, and post a stitched "main | this PR" composite for only the views that CHANGED. A PR with no visual impact composites nothing → "no visual change". This makes the preview feature-aware with no per-PR understanding: the diff finds exactly the surface the PR moved (and it subsumes the backend-PR noise #6959 pre-filtered, at the content level). - web-shell-visuals-compose.mjs: pixel-diff (canvas) + stitch a labelled composite; pure helpers (parseShot/isChanged/planWork) unit-tested. - web-shell-visuals.yml: also render the base — trusted `main` via pull_request.base.sha, so no secret exposure in the untrusted-PR job — then compose; composites replace the raw after-shots (same `<view>-<theme>.png` name the publisher already expects). - publish buildComment: list composites; "no visual change" when none. Verified locally by overlaying #6881's real changes onto main + a new mermaid scenario: the compositor flagged the mermaid view 6.5% changed (its new zoom controls) and correctly skipped the unchanged transcript. * ci(web-shell): address before/after review — lazy import, merge-base, robustness Addresses the /review findings on the before/after preview: - Lazy the @playwright/test import in the compositor so the pure exports (parseShot/isChanged/planWork) load dependency-free, and wire web-shell-visuals-compose.test.mjs into the github_ci_only test step — it was never actually running in CI. (finding 1) - Diff against the MERGE-BASE, not the base-branch tip, so a PR branch behind main doesn't render others' already-landed changes reversed as this PR's diff. (finding 2) - continue-on-error on the base checkout + install so a flaky base degrades to after-only instead of sinking the job; compose likewise degrades to the raw after-shots on failure. (finding 3) - timeout 20->30 (the job ~doubled) and scope the base render to screenshots.spec.ts, skipping the discarded flow videos. (finding 4) - diffPct: add img.onerror so a corrupt/truncated baseline PNG can't hang page.evaluate to the job timeout. (finding 5) - Lower CHANGED_PCT_THRESHOLD 0.1 -> 0.02 (~205px at 1280x800) so an icon swap or one-word label change isn't classified "no change". (finding 6) - Nits: correct the stdout comment, esc() the burned-in labels, and scope the comment wording to "screenshots" (flows are always head-only). * ci(web-shell): address before/after review round 2 (yiliang114) - diffPct: a dimension change IS a visual change — comparing only the overlapping rectangle hid it (a taller viewport with unchanged top pixels read 0%). Short-circuit any size mismatch to changed. (Critical) - Composite/comment label: "PR base (before)" not "main" — the workflow also runs for release/**, whose base is not main. (Critical) - Merge-base resolve: retry the compare API, then emit an EMPTY sha and SKIP the base render (after-only) rather than falling back to the base-branch tip, which reintroduces the reversed-diff bug. (Critical) - Base steps get ids; the before render runs only when the base checkout AND install both succeeded — else base/ (nested under head) resolves node_modules up to head's and produces a hybrid before. (Critical) - Publisher: a zero-change run now UPDATES the marker comment (image-less "no screenshot changes") instead of exiting, so a prior preview's stale images + SHA do not linger. (Critical) Finding 6 (helper tests skip full CI) is a pre-existing repo-wide gap for every .github/scripts test; left for a focused follow-up. * ci(web-shell): close the compositor browser in a finally (leak on error) A mid-loop rejection in diffPct (evaluate timeout / CDP disconnect on a corrupt or oversized PNG) exited composeCli via the exception and skipped browser.close(), leaking a ~200 MB Chromium child for the rest of the CI job. Wrap the page + loop in try/finally so the browser always closes. Also drop the stale "main (before)" labels from the docstring (the composite/comment say "PR base" now, since the workflow also runs for release/**). * ci(web-shell): catch the compositor CLI promise for a clean exit An unhandled composeCli() rejection (e.g. a missing @playwright/test) printed an UnhandledPromiseRejectionWarning and exited without a meaningful code; add a .catch that writes the error and exits 1. * ci: run .github/scripts helper tests in full CI + test planWork nullish guards - Finding 6: the compositor/publisher helper tests ran only in the github_ci_only profile, which a `full` PR skips (and vitest test:ci doesn't collect node:test files) — so a compositor change could pass CI without its regression tests. Run them in the full ubuntu Test job too. - Cover planWork's `?? []` guards with null/undefined inputs. * ci: extract HELPER_TESTS list so both CI profiles share one source of truth Round-3 F6 fix duplicated the .github/scripts node:test list across the github_ci_only and full-profile steps; a missed edit would silently drop coverage in one path. Hoist it to a workflow-level env var both reference. --------- Co-authored-by: wenshao <wenshao@example.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> |
||
|
|
51026d4d06
|
ci(serve): daemon A/B before/after preview on response-surface PRs (#6975)
* ci(serve): daemon A/B before/after preview on response-surface PRs The daemon analog of the web-shell visual before/after bot. For PRs that touch the qwen serve response surface, build the CLI from BOTH the PR base (main) and head, drive a fixed set of endpoints against each daemon, and diff the JSON responses into a before/after field table posted on the PR. A PR with no response change -> "no change". - serve-ab-diff.mjs: structural JSON diff (set-aware arrays, volatile- field masking) -> before/after table; buildComment assembles all scenarios. Pure helpers unit-tested (12 tests). - serve-ab-drive.mjs: boots the built daemon, drives GET /health, /health?deep=1, /capabilities (no model -- dummy creds), captures JSON. - serve-ab.yml / serve-ab-publish.yml: capture (untrusted, no secrets) + privileged workflow_run publisher (validate+bind PR, dedup, TOCTOU), mirroring the web-shell split. Verified locally: real daemon boot + capture, the engine diffing real capabilities -> the before/after table, and no-change detection. The workflow wiring (2x build + drive in CI) is exercised on the next serve PR. * ci(serve): address before/after review — merge-base, base fail-safe, CI-wire tests Applies the transferable web-shell before/after review findings to the daemon A/B: - Diff against the MERGE-BASE, not the base-branch tip, so a PR branch behind main doesn't show others' already-landed daemon changes reversed as this PR's diff. - continue-on-error on the base checkout so a flaky base degrades to after-only instead of sinking the job (the base build/drive already was). - Wire serve-ab-diff.test.mjs into the github_ci_only test step — it uses only node builtins, so it runs there dependency-free (no npm ci needed). * feat(serve): add a create-session deep-health A/B scenario Add a `health-deep-with-session` scenario: the drive harness gains setup-request support, creates one session (POST /session), then probes GET /health?deep=1 — exercising the session lifecycle + cross-workspace session aggregation (#6961's exact case). Broaden the volatile mask to cover lastActivityAt/idleSinceMs/sessionId/clientId/workspaceCwd so the capture is deterministic, while the meaningful counts (sessions, activePrompts, pendingPermissions, connectedClients, channelAlive) stay diffable. Verified against the real daemon: two runs of the same build diff to "no change" (deterministic), and a sessions 1->2 change surfaces as a before/after table row. * ci(serve): address review — narrow trigger, degraded-base marker, test the entrypoint - Narrow the trigger from packages/core/src/** (~1100 mostly-unrelated files) to packages/cli/src/serve/** — the daemon's actual response surface (core has no serve/health subtree). (Suggestion) - diffCaptureDirs signals baselineMissing when head captures exist but the base build/drive produced none, so the comment says "diff skipped" rather than misreporting every field as added or "no change". (Suggestion) - Export + test diffCaptureDirs (the function the CI `comment` subcommand actually invokes) with temp-fixture dirs, covering the diff + degraded path. (Suggestion) - Wording: "PR base" not "main" (the workflow also runs for release/**). * ci(serve): address review round 2 — escape table values, guard setup/baseline/daemon - fmt escapes `|` and backtick so an arbitrary daemon value can't split a GFM table cell or close the code span. - diffCaptureDirs surfaces base-only (removed/failed-to-capture) scenarios instead of silently dropping them — otherwise a broken scenario lowers the "across N" count and masks the regression. - The drive checks each setup request's status and throws on non-2xx, so a failed POST /session can't let health-deep-with-session capture wrong state (0 sessions) and fake/mask a diff. - The drive awaits daemon exit after SIGTERM (SIGKILL after 5s) so a hung daemon can't linger. * ci(serve): clear-error JSON parse for both capture sides The after-side JSON.parse was unguarded while the before-side wasn't; a readJson helper now gives a 'invalid JSON capture at <path>' error for either side instead of a raw SyntaxError. * ci(serve): review round 3 — merge-base skip, surface malformed base, escape path, kill dead stdout - serve-ab.yml: retry the merge-base compare then SKIP the before build (after-only) instead of falling back to the base-branch tip; gate the base checkout/build on a resolved sha + successful checkout. - diffCaptureDirs: an existing-but-malformed base capture now surfaces (existsSync + readJson) instead of a bare catch treating it as {} (which reported every field as "added"); covered by a new test. - renderTable: escape backticks in the path field too (fmt already did for values). - Drop the dead stdout + its misleading "workflow reads it" comment (the publisher posts whenever body.md exists); log the count to stderr. * ci(serve): renderTable label 'PR base' not 'main' (release/** provenance) buildComment already said 'PR base', but the per-scenario table header + no-change line still said 'main'; align them (the workflow also runs for release/**, whose base is not main). --------- Co-authored-by: wenshao <wenshao@example.com> |
||
|
|
02c79beb62
|
feat(web-shell): auto-post visual previews (screenshots + flow GIFs) on PRs (#6880)
* feat(web-shell): auto-post visual previews (screenshots + flow GIFs) on PRs PRs that touch the web-shell UI now get an auto-updated comment with light/dark screenshots of key views (transcript, slash menu, model/theme dialogs, permission panel) and short GIF recordings of common flows, rendered against the existing mock daemon — no real backend, no secrets. Split into two workflows for security, since capture runs untrusted PR code: - web-shell-visuals.yml (pull_request): checks out the PR head, builds and renders it with Playwright, captures PNGs + webm, converts webm->GIF with ffmpeg, and uploads an artifact. `contents: read` only, references no secrets — fork PRs run with a read-only token and no secrets. - web-shell-visuals-publish.yml (workflow_run): downloads the artifact, binds it to its real PR by requiring the PR head SHA to equal the run's authenticated head SHA, hosts the images on a per-PR `pr-assets/*` branch (referenced by immutable commit SHA), and posts/updates one inline comment. Never checks out or runs PR code; the write token lives only here. Capture infra is self-contained in packages/web-shell (playwright.visuals.config.ts + client/e2e/visuals/*), reusing the mock daemon harness. Run locally with: `npm run test:e2e:visuals --workspace=packages/web-shell`. * fix(web-shell): guard empty gh api response in visuals publish Addresses review feedback on #6880: if `gh api` returns empty (network error / rate limit), jq on empty stdin errors and `set -e` kills the publish job. Skip gracefully instead. * fix(web-shell): address review nits on visuals capture - harness recordFlow: wrap video saveAs/delete in try/catch so a video I/O error (e.g. drive failed before navigation) can't mask the real driveError. - capture workflow: drop the unused head_sha.txt artifact field; the publish job binds to the authenticated workflow_run.head_sha, and an artifact-sourced SHA would be untrusted. * fix(web-shell): address second review round on visuals capture - context.close() in recordFlow's finally is now best-effort (try/catch) so a close/crash error can't mask the real driveError. - add a flows spec that asserts a throwing drive propagates its own error. - trigger the capture workflow on playwright.visuals.config.ts changes too. * fix(web-shell): address third review round on visuals capture - harness: log (don't silently swallow) a video save/null when drive succeeded; keep masking-suppression only when driveError is set. - publish: HTML-escape interpolated values in the comment builder (defense in depth, independent of the upstream filename sanitization); fix the stale 'single pr-assets branch' comment and key concurrency on source repo+branch so different PRs (incl. same-named fork branches) parallelize. - capture: bump checkout to v6.0.3 (repo standard); surface ffmpeg's stderr on GIF-conversion failure instead of discarding it. * fix(web-shell): harden visuals publish/capture (review round 4) Publish (privileged workflow_run): - CRITICAL: capture basename before `tr` so its trailing newline isn't turned into `_` (which broke the .png/.gif filter -> empty preview). - dedup only against the bot's OWN comment (author + marker), not any marker-bearing comment a participant can post. - bound the pr-assets branch: force-push a single orphan snapshot per run (previous snapshot GC'd) instead of appending unbounded untrusted content; this also removes the rebase/retry path. - cap EXAMINED candidates (not just accepted) before validation; tighten per-file (3MiB) and accepted-image (14) caps. - re-validate PR open + head-SHA immediately before the comment write (TOCTOU); retry the comment listing and abort rather than POST a duplicate when listing fails. - esc() the runUrl for consistency with the self-defending HTML. Capture (pull_request): - upload raw recordings as a SEPARATE artifact the publisher never downloads, so an untrusted multi-GB video can't exhaust the privileged job. - also trigger on packages/webui/src and packages/sdk-typescript/src (the visuals dev server aliases them). - create screenshots/gifs dirs before the metadata counts (defensive). Harness recordFlow: - track drive failure with an explicit boolean (handles `throw undefined`); discard the recording on failure so a failed flow leaves no bogus webm. * refactor(web-shell): extract + unit-test the visuals publish staging/comment Addresses the review's testability gap (the class of bug that let the filename sanitizer break the whole preview slip through green CI). The image validation (magic bytes, filename sanitization, examined/accepted/size caps) and the comment builder (light/dark pairing, flow labels, HTML escaping) move from inline workflow bash/node into .github/scripts/web-shell-visuals-publish .mjs, covered by web-shell-visuals-publish.test.mjs (run in ci.yml's node --test line). The publish workflow sparse-checks-out and calls the script instead. Behaviour is unchanged; it just gained a test surface. * fix(web-shell): retry the visuals asset force-push; drop stale comment Round-4 switched hosting to a force-push but left a comment referencing a 'push-retry loop' that no longer existed, and the force-push was a single call that set -e would abort on a transient failure. Add a bounded retry and correct the comment. * fix(web-shell): harden visuals publish/capture (review round 6) Script (unit-tested): - flow labels: own-property lookup so `toString.gif`/`constructor.gif` can't leak Object.prototype members into the comment. - per-kind image caps (screenshots vs gifs) so a large screenshot set can't silently starve the flow GIFs from the preview. - tests for both, plus the per-kind cap. Publish: - bind the artifact PR number to the run's authenticated head repo+branch (not just head SHA), rejecting a sibling PR that shares the same commit. - re-validate before the force-push and again right before the comment write (close the download/stage/lookup TOCTOU windows). Capture: - bound artifact contents before upload (drop oversized / excess files) so an untrusted spec can't bloat the published or video artifact. - trigger on the capture workflow file itself. - new close-trigger cleanup workflow deletes a PR's asset branch on close, so pr-assets/* refs don't accumulate without bound. - single-source the capture viewport (constants.ts) shared by config + harness. - model-switch flow asserts the daemon model request actually fired. * fix(web-shell): stricter visuals error handling (review round 7) Harness recordFlow: - when the drive SUCCEEDS, a failed context.close() or video.saveAs() (or a missing recording) now FAILS the flow instead of a swallowed console.warn — a silent pass with no .webm makes the downstream GIF step fail confusingly. A drive FAILURE still discards the partial video and rethrows the original error (unchanged). Publish: - validate_pr distinguishes a transient API failure (empty after retries -> exit 1, re-triggerable) from a genuine invalid state (closed / head mismatch -> skip), via a `gate` wrapper used at all three checkpoints. - add a 2s backoff between comment-listing retries (matching the push retry). --------- Co-authored-by: wenshao <wenshao@example.com> |
||
|
|
d4c15f05c5
|
feat(ci): add automated PR failure patrol (#6766)
* feat(ci): add stale failure patrol * fix(ci): harden failure patrol * refactor(ci): simplify flaky rerun patrol * docs(ci): clarify flaky patrol skill boundary * feat(ci): patrol stale PR failures * fix(ci): prefilter failed PRs * fix(ci): isolate patrol classification * fix(ci): revalidate stale patrol actions * fix(ci): verify main before branch update * fix(ci): classify all stale PR failures * fix(ci): bound patrol batches * fix(ci): persist patrol failure state * fix(ci): harden patrol state transitions * fix(ci): harden stale failure patrol * fix(ci): continue patrol after expired logs * fix(ci): tighten patrol guardrails * fix(ci): preserve failure context in patrol logs * fix(ci): harden stale patrol closeout * fix(ci): paginate patrol marker comments * fix(ci): harden patrol action guards * test(ci): cover patrol guard rails * fix(ci): harden patrol marker parsing * fix(ci): address patrol review followups * test(ci): cover patrol review edges * fix(ci): record patrol rerun marker first * fix(ci): harden patrol review edge cases * fix(ci): tighten stale failure patrol markers * refactor(ci): simplify flaky rerun patrol (2838→1258 lines) - Remove classification guards from actOnDecision (confidence check, action enum validation, boundedReason, update_branch multi-guard chain) - Move classification rules to SKILL.md prompt - Delete 40 source-code text matching tests, keep 20 behavior tests - Merge identity job into classify, remove SHA verification - Change scan sort order from oldest-first to newest-first - Remove unused functions: writeSkillInputs, failureKey, boundedReason, canAct, skillCandidate, mainRunSucceeded * fix(ci): show gh stderr in top-level error output When gh CLI returns non-zero exit, execFile rejects with an error whose .stderr contains the actual GitHub API diagnostic. Previously only one of stderr or message was shown; now both are printed. * fix(ci): address patrol review findings * fix(ci): make stale patrol actions recoverable * refactor(ci): simplify flaky rerun patrol * fix(ci): close flaky patrol review gaps * fix(ci): restore PR failure patrol actions * fix(ci): harden failure patrol scanning * fix(ci): address patrol review follow-ups * fix(ci): harden patrol parsing and coverage * fix(ci): classify failures against PR changes * fix(ci): harden patrol script input handling * fix(ci): remove unsafe auto branch update * test(ci): exercise patrol action limit * fix(ci): bind patrol actions to current evidence * fix(ci): count patrol actions per PR * fix(ci): redact quoted secret labels --------- Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
e1fc45d508
|
ci(autofix): restore sandbox image flow (#6261)
* ci(autofix): restore sandbox image flow * test(ci): update autofix workflow assertions * ci(autofix): restore tracked output before review checkout * test(ci): clarify autofix workspace assertion * style(ci): format sandbox image resolver * ci(autofix): address sandbox review feedback * test(ci): cover sandbox image publish gating * ci(autofix): validate sandbox image config * ci(autofix): address workflow review comments |
||
|
|
183ad54b11
|
ci: add lightweight PR profiles (#6186)
* ci: add lightweight PR profiles * fix(ci): harden lightweight profile classification * fix(ci): harden lightweight profile edge cases * fix(ci): report classifier invocation failures * fix(ci): tighten docs-only path matching * ci: restrict light profiles to same-repo PRs --------- Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
e743f0e2e0
|
ci(autofix): run agents on dedicated ECS runners (#6207)
* ci(autofix): run agents on dedicated ECS runners * ci(autofix): retry repository checkout * ci(autofix): use local qwen bundle fallback * ci(autofix): harden ECS runner failure handling * ci(autofix): retry transient qwen runner failures * fix(ci): proxy autofix model credentials * fix(ci): limit ECS autofix to ready issues * fix(ci): address autofix workflow review feedback * fix(ci): restore autofix known bots * chore(ci): trim autofix ECS scope * fix(ci): consolidate autofix OpenAI proxy * fix(ci): restrict autofix agent execution tools --------- Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> |
||
|
|
89ddc701a9
|
fix(ci): limit fork PR precheck to safety signals (#6178)
* fix(ci): narrow fork PR precheck gating * fix(ci): scan PR text for leaked secrets * test(ci): avoid token-shaped precheck fixtures * fix(ci): trust write-access fork authors in precheck * fix(ci): address precheck review findings * fix(ci): resolve precheck review findings * fix(ci): catch multiline secret sink args |