mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-21 22:55:16 +00:00
8676 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
dd6b7824dd | fix(cli): harden agent view lifecycle recovery | ||
|
|
d0fa70a1a2 | fix(cli): serialize stopped session healing | ||
|
|
b0b103c57b | fix(cli): close agent view lifecycle races | ||
|
|
9a7bad886e | fix(cli): make agent view lifecycle recoverable | ||
|
|
6432b72fa9 | fix(cli): close agent view lifecycle races | ||
|
|
eccde8caed |
Merge remote-tracking branch 'origin/agent/agent-view-pty-workers' into codex/pr-7801-closeout
# Conflicts: # packages/cli/src/agent-view/pty-host-process.test.ts # packages/cli/src/agent-view/pty-host-process.ts |
||
|
|
3c63475d29 | fix(cli): wait for PTY host endpoint shutdown | ||
|
|
70ead6d664 | fix(cli): close agent view prompt recovery races | ||
|
|
f8961af8e3 | fix(cli): serialize agent view lifecycle transitions | ||
|
|
6fc40ee248 | fix(cli): harden agent view lifecycle races | ||
|
|
ff20a1acdd |
fix(cli): Confirm worker death before terminal verdicts and close roster fail-open paths
Round-16 review fixes for the Agent View lifecycle: - Roster strict reader now rejects a declared-but-non-boolean pinned field; roster mutations read through the same fail-closed path so a corrupt-but-present roster.json can no longer be overwritten with an emptied roster (pins exist nowhere else). - The dead-worker event guards also drop events against 'hibernating' records, so a straggler event after the sweep's point of no return cannot flip the record back to alive and fail the hibernated mark. - respawn releases the predecessor's registry entry before launching, keeping the graceful-stop fallback timer from matching the retired host mid-launch. - Worker events normalize waitingFor case at ingest so the queued- prompt dequeue gates and presentation agree. - shouldAdvanceActivityTime stops advancing lastActivityAt while an input control suppresses the marker dequeue, keeping the stale-marker wall-clock evidence honest. - Hibernation sweep, stop fallback, remove, and shutdownAll confirm the actual exit (shutdown RPC with SIGKILL escalation, then host.exited) before writing terminal verdicts: a still-draining worker must not keep the session socket with no signalling path able to reach it. |
||
|
|
878eedad25 |
Merge agent/agent-view-pty-workers into agent/agent-view-lifecycle
Resolve pty-host-process.ts: keep the base's kill() settle-after-RPC refinement; for shutdown() keep the R15-5 semantics (no exit verdict — the shutdown RPC returns when the drain starts, which can be survived, so the remote exit poller reports the real outcome). |
||
|
|
b14082ec61 | fix(cli): Harden Agent View lifecycle verdicts, ghost cleanup, and resume argv | ||
|
|
4c21f39ac0 | fix(cli): close R14 respawn EADDRINUSE, dispatch pid timing and stale verdict races | ||
|
|
adc3bbf419 | fix(cli): close R13 verdict races, resume casing and stop/respawn lifecycle gaps | ||
|
|
b78e37444a |
fix(cli): close R12 stop-launch orphan, marker clobber and pid-signaling races
- terminate orphan worker when dispatch/adopt stop errors bypass queueStop - drain queued stop control before hibernate; re-validate pin under roster lock - serialize worker file writes through the shared per-path mutation queue - make dequeue/state heals marker-guarded and skip terminal sessions - sanitize adopted session ids once; guard stale pid signaling |
||
|
|
8572f36818 | Merge remote-tracking branch 'origin/main' into codex/qwen-code-pr-7800 | ||
|
|
7091b8c761
|
fix(review): lock the PR review worktree lease against concurrent sessions (#9211)
* fix(review): lock the PR review worktree lease against concurrent sessions The /review worktree lives at a fixed path per PR number, and the lease recording its owning session was only consulted by the end-of-session crash sweep. A second session reviewing or finishing the same PR deleted the first session's worktree, branch, and side files mid-run (#9205). Make the lease double as a lock: fetch-pr refuses with an actionable error before touching anything when another session holds it, and cleanup skips the whole target with a note. Ownership is per session, so drift restarts and later rounds of a multi-prompt review are not locked out. A missing worktree now fails repo-context with a re-run-fetch-pr message instead of a bare ENOENT. * fix(review): roll back the lease on fetch-pr failures and scope the missing-worktree remedy Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(review): shield live leases from the cleanup sweep and roll back on any fetch-pr failure * fix(review): re-check the lease after cleanup's audit to close a TOCTOU (#9211) - Re-read the lease after the network-bound bypass audit and before any destructive step, so a session that acquires the lease during the audit is skipped, not destroyed (R2-10). - Narrow the cleanup lease-skip guard to the real lease shape so a target named 'lease' still has its own side files swept (R2-1). - Make the fetch-pr lease rollback best-effort via tryRemove so an un-removable lease file cannot mask the original failure (R2-5). - Pin the lease-lock wiring and success/rollback invariants in tests (R2-2, R2-7, R2-8, R2-11). * fix(review): validate fetch-pr's number and release leases off side-file residue (#9211) The lease gate only engaged `pr-\d+` targets while cleanStale destroyed worktreePath(prNumber) for any input, so a malformed number bypassed the lock and deleted a live holder's worktree; refuse non-positive-integer pr_number before the gate like the sibling commands. Cleanup now releases the lease once the worktree and branch steps succeed instead of holding it on an un-deletable side file, which wedged every later review of the PR. The lease-file grammar is one shared predicate (isReviewLeaseFile) across the writer, the sweep guard, and the finalizer scan, and the lease tests pin the arguments and ordering the mocks previously left blind. * fix(review): acquire review leases atomically and fail closed on identity (#9211) Close the round-5/6 lease-lock findings: - Create the lease with `flag: 'wx'` so two concurrent fetch-prs that both pass the gate's read cannot clobber each other's lease; on EEXIST, same-session re-fetch rewrites, a foreign holder refuses (R6-1). - Roll the lease back on failure only when this run created it, and compare ownership before deleting so a re-fetch keeps the session's live lease and a lease acquired during a stuck run survives (R6-2). - Refuse fetch-pr before any state when QWEN_CODE_SESSION_ID / QWEN_CODE_PROMPT_ID are absent instead of running lease-less (R6-3). - Register the lease inside the rollback try (R6-5). - Track the platform separator in the lease assertion (R6-4) and gate the POSIX-only ENOTDIR test off Windows (R5-1). - Pin the `Number(prNumber) <= 0` validation disjunct (R5-2) and arm the side-file sweep in the lease-skip test (R4-3). --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: qwen-code-autofix[bot] <qwen-code-autofix[bot]@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
d5b26b411f
|
fix(review): close out the four leftover findings from the #9222 review (#9270)
* fix(review): close out the four leftover findings from the #9222 review - findings refuses a --to-anchors that resolves to the same file as --input/--out/--outcomes/--test-delta: the resolver input would silently destroy its counterpart while stderr reports every write as successful, and Step 7 joins the pair by id. - the --to-anchors flag now has a wiring gate: every command-boundary test built argv by hand with the camelCase key, so a mismatch between the declared option name and the handler's read would stay green — the new test parses through the actual yargs builder and feeds the parsed object to the handler. - the final absence reason's rewrite gets a distinguishing assertion: the existing toContain check matches the pre-PR wording too, so a revert would leave the suite green while the reason stopped naming the fragment shape. - the shape-2 recovery map gains the substring tier's exact-multiplicity reason: the generic more-lines remedy cannot recover it because the containment tier accepts single-line snippets only; a longer same-line fragment or the line number does (witnessed by probe in the #9222 thread). * fix(review): compare file identity in the findings collision guard and pin its tests * fix(review): harden the findings collision guard to filesystem identity The guard compared path strings — realpathSync where a file existed, lexical resolve() otherwise — so it admitted exactly the collisions it exists to refuse: hard links are two names of one inode that no realpath resolves, a dangling --out symlink aliasing the not-yet-created --to-anchors target rode the "absent file" fallback, and a symlinked directory component aliased absent paths on both sides. Compare dev/ino where both sides exist, canonicalise the deepest existing ancestor where one does not, refuse a dangling sibling link outright, and extract the check into review/lib, replacing the two byte-identical private sameFile copies in repo-context and save-artifact. * fix(review): refuse nested --to-anchors collisions and pin the guard migrations |
||
|
|
90c166585a
|
feat(release): user-facing bilingual digest for release notes (#9216)
* feat(release): user-facing bilingual digest for release notes Stable release notes read as a type-bucketed PR list, which users find hard to scan. The finalize step now asks the model to group changes into user-facing themes with short intros, mirrors highlights and themes into a Chinese digest, attaches screenshots found in merged PR bodies (host allowlist, per-release cap), and collapses the full PR list into an appendix with normalized titles. Every model failure path keeps today's v1 output byte-for-byte, and CHANGELOG.md accepts the new v2 marker. * fix(release): tighten v2 digest fallbacks and changelog skeleton (#9216) Address review round 1 findings: - usedAi only counts themes that carry content, so a release whose digest has zero model text is no longer reported as AI-generated - hasChinese is derived from what the Chinese block actually renders, not raw model output, so zh-only-on-breaking releases no longer emit an empty or English-only section - a PR repeated inside one theme is deduped instead of discarding the whole themes digest with a misleading cross-theme error - fallback titles in the v2 digest are normalized like the appendix, killing the mixed-style look in the degradation case - normalizeAppendixTitle strips only the conventional types the changelog's formatEntry strips, keeping ci/test/security prefixes - the changelog unwraps the v2 appendix at the same sibling rank as v1's Complete Change List instead of nesting it under the previous section - drop a dead summaries max_tokens scaling term and a verbatim copy of renderChangeLine's attribution rendering * fix(release): close digest image breakout and tighten fallback signals (#9216) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(release): drop camo proxy and harden digest text validation (#9216) * fix(release): neutralize markdown breakouts in digest text and images (#9216) * fix(release): close classification, image-URL, and text-validation bypasses (#9216) --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
195128a17a
|
fix(daemon): Preserve sessions when active-work close is refused (#9134)
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 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
Security Checks / Dependency CVE audit (push) Waiting to run
Security Checks / Secret scan (TruffleHog) (push) Waiting to run
npm cache producer / Save npm cache (push) Has been cancelled
* fix(daemon): preserve active-work sessions on close refusal Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(daemon): guard deferred spawn-owner kill against in-flight closes The reaper can hold a conditional-close probe on a tombstoned entry for up to ACTIVE_WORK_CLOSE_TIMEOUT_MS; a deferred kill fired in that window bounces off the close gate of the child and killSession escalates the error to a channel kill, taking every sibling session down with it. Re-add the activeWorkCloseInFlight exclusion to the branch: the probe resolves the entry one way or the other, and a refusal leaves the tombstone to complete on the next settle event. Also extract sessionCloseDrainBudgetMs so the child drain budget lives in one place, report the shared drain budget instead of the phase-2 residue in the close timeout message, and pin the guards with tests (in-flight probe, in-flight force close, no abort on refusal, phase-1 settle timeout, close-gate release on the timeout path). Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(daemon): key sessionClose drain budgets to the actual outer wait Round-2 review follow-ups: - notifyAgentSessionClose derived the child drain budget from the initTimeoutMs default even when the caller applied a shorter outer wait (opts.timeoutMs): a condemned-channel close with an operator --initialize-timeout-ms of 30s told the child to drain for 24s while the daemon stopped listening after 10s, and the unknown outcome escalates to a channel kill. The budget now keys off the wait actually applied. - branchSession's partial-restore cleanup was the last sessionClose sender without a drainTimeoutMs; it now uses the shared helper. - The deferred spawn-owner kill branch uses the canonical isClosingOrAuthorizingClose predicate instead of an inline copy. - The conditional close's history-mutation wait now receives the shared-budget residue instead of a fresh full budget, keeping the round trip under the daemon's outer wait (the body stays untimed, so the guarantee is approximate); its timeout message reports the shared budget. - Tests: pin the conditional-close success path (abort ordered after the first settle via invocationCallOrder) and deduplicate the close-gate tracking mock into trackCloseGateHeld. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(daemon): tolerate definitive close refusals in killSession Round-3 review follow-ups: - killSession escalated ANY sessionClose error to a channel kill, but a child holding its own close gate (changeSessionCwd, restore — child-side state the daemon cannot observe) answers with a definitive RequestError. On that answer the kill now resets entry.closing and returns false, leaving the deferred tombstone to complete on the next settle event, instead of SIGTERMing every sibling session on the channel. Pinned by a test that flips the child from definitive refusal to acceptance and asserts the kill completes without a channel kill. - The deferred spawn-owner kill logs one stderr line before firing; its success path was previously unattributable in daemon logs. - sessionCloseDrainBudgetMs gains a literal unit test pinning the ratio strictly under the outer wait and the >=1ms clamp, so the documented invariants cannot drift with the implementation. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
889f0d8bbd
|
feat(daemon): Isolate the Conversations runtime boundary (#9181)
* feat(daemon): isolate the Conversations runtime boundary Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: fix CI failure on PR #9181 Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: fix CI failure on PR #9181 Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#9181) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#9181) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#9181) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#9181) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#9181) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#9181) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
f9e6b67aa7
|
fix(ci): stop triaging the autofix bot's own deferred-finding tracking issues (#9264) (#9271)
* fix(ci): stop triaging the autofix bot's own deferred-finding tracking issues (#9264) Every PR that defers findings for the first time opens a tracking issue upserted by the autofix bot, and the issues trigger (opened/edited/reopened) ran a full triage agent on that bookkeeping issue per deferral — the authorize gate exempts the issues path as read-only, so nothing stopped it. Condition the triage job's issues clause on the creator not being the autofix bot (the same vars.AUTOFIX_BOT_LOGIN || 'qwen-code-dev-bot' identity qwen-autofix.yml upserts under), and route bot-created issues runs to a per-run concurrency group: GitHub evaluates concurrency before the job if, so a run left in the shared per-number group would still cancel an in-progress triage of the same issue before its own skip is evaluated. Pins: the issues-clause guard, the group routing, and the cross-workflow identity sync, all on the parsed document. * test(ci): pin triage issue guard connectors * test(ci): harden triage bot guard pins --------- Co-authored-by: yiliang114 <yiliang114@users.noreply.github.com> |
||
|
|
f7f78fab4a
|
fix(ci): force-push release branch so retries replace failed attempts (#9076) (#9082)
* fix(ci): force-push release branch so retries replace failed attempts (#9076) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(ci): re-validate the release version before force-pushing (#9076) prepare's doesVersionExist check runs minutes to hours before publish pushes (validation jobs and the production-release approval gate sit in between), and --force removed the non-fast-forward rejection that used to serialize the push itself. Concurrent same-version runs could therefore diverge the npm artifacts, the git tag, and main. Serialize publish per release tag and re-validate the unshipped invariant — every published package, the tag, and the release — immediately before the push; pin all three invariants in the workflow tests. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(ci): unify the push-time release guard and key concurrency by dry-run (#9076) * fix(ci): fail closed on push-time release probes and test the CLI seam (#9076) * fix(ci): clarify push-time release refusals and keep benign ones out of autofix (#9076) The push-time guard refused retries after a partial npm publish without saying where the version had shipped or how to recover, and every refusal failed the publish job into notify_failure, filing a "Release Failed" issue and dispatching the autofix agent against releases that did not fail. - Scan all published packages in strict mode and name every shipped location in the refusal (npm packages, origin tag, GitHub release) with partial-publish recovery guidance; a decisive hit ends the check so a flaky later probe cannot mask the refusal with a probe error. - Give the guard distinct exit codes: 3 = already shipped (decisive, benign), 2 = probe or usage failure. Exit 1 is reserved for uncaught node errors so a crash can never masquerade as the benign marker. The push step marks exit-3 refusals via the version_refusal job output, and notify_failure skips its issue + autofix dispatch for exactly that failure while genuine failures still notify. - Cover runCli's default dispatch (prepare's path), the exit-code contract, and the process.exit wiring end to end. * fix(ci): fail closed when the release ref predates the push-time guard (#9076) * fix(ci): keep refusals decisive after shipped hits and skip the POSIX-only test on win32 (#9076) * fix(ci): write push-time guard error annotations to stdout (#9076) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
f2a6e6c727
|
feat(review): validate and scope the incremental anchor inside fetch-pr (#9100)
* feat(review): validate and scope the incremental anchor inside fetch-pr
The incremental path was the one diff this skill still asked the
orchestrator to hand-compute: Step 1 said 'git diff <sha>..HEAD inside
the worktree', and the recovered-anchor bullet asked for hand-run
cat-file / merge-base --is-ancestor checks — the exact shape the skill
forbids everywhere else (the diff is a file the CLI writes, never a
command a run can skip or get wrong).
fetch-pr gains --since <sha>: a hex allowlist first (an anchor from a
cache file or a posted marker is never handed to git flag-shaped),
then cat-file/is-ancestor/rev-parse behind an injectable probe, ruling
one of: effective (diff and chunk plan scoped to since..head),
upToDate (anchor is the head, or the commits since it change no bytes
— full-range plan kept, because the flows that continue past an
up-to-date anchor run full), or refused with the reason
(not-an-ancestor for a rebase/force-push, unknown-commit,
capture-failed) and a full-range fallback. The decision is the
report's incremental field; emptyDiff and collapsedFromUpstream are
skipped on a delta scope — both compare the full merge-base range
against GitHub's advertised full-PR stat, and a delta is always far
smaller, so the collapse ratio would fire on every incremental review.
SKILL.md Step 1's two incremental bullets now read the report instead
of running git: the cache is read BEFORE fetch-pr and its
lastCommitSha passed as --since; the marker-anchor path re-runs the
same command with --since appended once the side file lands.
* fix(review): keep the empty-delta capture out of emptyDiff, and scope Agent 7 to the delta
Six findings from this PR's dogfood review round:
- Critical: the empty-delta capture set diffPath, and the full-range
fallback can fail or never run (no merge base) — the leaked path made
isEmptyDiff read 'captured the full range, and it is empty' and
recommend a live PR for closure on an infrastructure state. The
fallback now resets diffPath/diffPathAbsolute first, and an upToDate
ruling whose full-range capture did not survive is demoted to
capture-failed rather than left promising a plan the report does not
carry. Both pinned through the real handler.
- Agent 7's test-efficacy probe welded --base mergeBaseSha into its
brief regardless of scope; on a delta round it would spend the probe
budget reversing already-reviewed hunks and report survivors outside
the scoped diff. The report's incremental ruling now carries diffBase
(the delta's left side, full sha) and the role-7 brief prefers it.
- The side-file recovery gate lost the refused-cache-anchor fallback in
the rewrite: a force-pushed PR with a newer round posted elsewhere
never consulted the marker's still-valid anchor. Restored, with the
already-refused sha excluded from the re-run.
- The setup-batch ordering contract gains the new edge: a side-file
fetch-pr --since re-run must precede repo-context, whose in-place
enrichment the re-run's from-scratch report write would discard.
- The pr-context ledger hint and the ledger.ts SHA_RE docstring still
instructed the hand-run validation this PR deletes; both now point at
the fetch-pr --since re-run.
- The four handler branches are now integration-tested through the
mock harness (mutation-verified: dropping the scopedDelta gates fails
the valid-delta case), and the builder pins --since registration.
* fix(review): clamp the anchor to the merge base, demote every planless ruling, and pin the seams
Six findings from this PR's second dogfood review round:
- The anchor was validated against the head but never the merge base:
an anchor older than the base (a partial merge landing between
rounds, or a tampered deep-history sha) scoped anchor..head — the PR
plus a slice of base history, whose comments 422 the whole Create
Review call. resolveIncrementalAnchor now takes the merge base and
refuses such an anchor as behind-merge-base; null skips the clamp.
- captureRange published diffPath before any caller accepted the
capture — the round-1 Critical fix was a caller-site reset guarding
producer state. The capture now returns text only and the two
accepting sites publish; the reset disappears structurally.
- A buildDiffPlan partition failure on a delta left the incremental
ruling standing over a diff-less, zero-chunk plan. The catch demotes
it (scopedDelta stays true so the full-range flags cannot fire over
the delta text).
- The demoted-upToDate shape conflated two failures under
capture-failed and its stderr claimed 'reviewing the full diff' with
no diff in existence. It gets its own reason
(full-range-unavailable), the status line is emitted after planning
and keys on the final ruling, and the SKILL bullet routes it to the
diffPath:null degraded state.
- The role-7 --base selection and the git-probe invocation shapes were
unpinned (both mutation-measured green): agent-prompt tests now
assert --base <diffBase> on a scoped round and the merge-base
fallback on upToDate; the handler tests assert the exact cat-file /
is-ancestor / rev-parse call shapes plus refused-anchor and
behind-merge-base rounds end to end, and the partition-failure
demotion through a steerable buildDiffPlan.
* fix(review): one planless reason, a tree-based empty ruling, and a base the clamp can trust
Six findings from this PR's third dogfood review round:
- Critical: three shapes published capture-failed over a zero-chunk
plan while the reason contract promised the full range was in hand
(partition failure; delta throw with the fallback also throwing;
delta throw with no merge base). Any refusal ending with no captured
diff now reports full-range-unavailable — one reason names the
degraded flow — and the stderr line names the underlying refusal.
- Critical: the delta round suppressed emptyDiff, so a PR that
collapsed to empty between rounds (head tree == merge-base tree while
anchor..head stays non-empty) was never recommended for
close-as-superseded and fanned agents over hunks GitHub's empty PR
diff does not contain. A delta round now rules emptiness by TREE
comparison — what the flag has always meant — under the same
stale-base guard as the text path.
- The behind-merge-base clamp ruled on a base the run had flagged
unreliable; a stale base now refuses the anchor (base-untrusted),
matching isEmptyDiff and isCollapsedFromUpstream, which both decline
to rule in that state.
- The anchor-shape predicate was a second, case-divergent copy of the
ledger's SHA_RE; it is now imported, so a ledger-blessed anchor and a
cache-supplied one cannot be judged by two predicates.
- The demotion literal, written three times, is one demote() closure.
- No test expanded an abbreviated anchor, so diffBase: resolved ->
diffBase: since survived; the resolver now pins the expansion.
* fix(review): check hunk containment, read the full range once, and normalize the anchor flag
Sixteen findings from the fourth dogfood round (4 Critical, 12
Suggestion). The Criticals:
- An in-range anchor can still produce delta hunks the PR's own diff
does not contain: an 'undo per feedback' commit reverts previous-round
lines back to base content, and a comment anchored there 422s the
whole Create Review call. Ancestry cannot see this, so containment is
now checked on the hunks (hunksContainedIn, new-side ranges per file)
before a delta may be the round's scope.
- The recovery flow appended a second --since to a command that already
carried one; yargs folds a repeated flag into an array, which
stringified to 'shaA,shaB' and failed the hex gate, refusing a valid
anchor as unknown-commit with zero git probes. The CLI normalizes to
the last value and the SKILL says REPLACE, not append.
- An upToDate ruling whose full-range recapture succeeded but failed
partitioning was never demoted (guard 1 had already run; the catch
required scopedDelta; the post-plan guard required !effective).
- base-untrusted refused a fully validated anchor even with no base
resolved at all, contradicting the docstring's own 'a null sha skips
the clamp' — the delta range needs no base.
The structural change under them: the full range is READ ONCE up front
whenever a base exists, and is the containment oracle, the fallback,
and the quantity emptyDiff/collapsedFromUpstream are defined against
(both had been judging a delta — the collapse ratio fired on every
incremental round, and an emptied PR went unflagged). That also lets a
delta the partitioner refuses retry the full range instead of ending
the round diff-less, under a reason that names it (partition-failed).
Tests: hunk containment, the emptied-PR round, base-untrusted wiring
and its null-base carve-out, the partition retry and the both-fail
case, upToDate-outranks-base-untrusted, and toEqual on the
resolveCommit-null refusal. reportFor now reads the LAST report — a
two-round test was asserting against the first.
* fix(review): make the containment oracle a real diff parser, and pin what it rules
Thirteen findings from the fifth dogfood round. All three Criticals were
the same defect class in the hunk-containment parser this PR added: it
recognized structure by prefix with no hunk-state tracking, so it could
be spoofed, and it saw only sections that carry hunks.
- An ADDED line shaped like a `+++ b/path` header (an embedded diff
fixture is exactly that) was read as structure and re-attributed every
later hunk — the oracle corrupted in both directions. Both sibling
parsers in this codebase already guard it (countDiffChangedLines
tracks inHunk; parseAddedLines documents the hazard); this one now
consumes hunk bodies by their declared line counts and recognizes
structure only outside them.
- A whole-file deletion contributed no new-side range and passed
vacuously, so a delta whose only content is a file the PR's own diff
never mentions became the review's scope.
- Sections with no hunks at all — mode-only, binary, pure renames —
were invisible for the same reason.
Sections are now keyed off the `diff --git` line (present in every
shape, including all three above), the path check is separate from the
range check, and a path this parser cannot name unambiguously fails
closed to the full range.
The rest: the dead tree-comparison seam in the test helper and the
stale comment naming a gate that no longer exists are gone; SKILL.md's
reason enumeration names every reason the CLI emits, and drops
partition-failed from the infrastructure-retry list (it re-fails
identically for the same sha and already carries a rescued plan).
New pins: seven hunksContainedIn cases (both boundary directions,
per-file isolation, the spoof, the deletion, hunk-less sections,
unparseable paths), a repeated --since whose abbreviation is resolved
end to end, the unknown-commit precedence when both probes fail, and
the two base-selection conjuncts agent-prompt never pinned.
* fix(review): fail closed when the containment oracle is lost, and pin what it parses
Eight findings from the sixth dogfood round.
The Critical: the containment guard failed OPEN when the full-range
capture threw while a base WAS resolved — `fullText === null`
short-circuited the conjunct and the delta published with the oracle
never run. That is the infrastructure failure the guard exists to
refuse, on exactly the large long-lived PR --since is for. It now
demotes; the base-FREE shape stays deliberate (no PR diff to be
contained in) and both are pinned.
Also from the round:
- Git C-style-quotes any non-ASCII path by default, which made the
parser return null and refuse every --since round on a PR touching
such a file — under a reason asserting DISPROVED containment.
`core.quotePath=false` is now pinned beside the other capture knobs,
and the ruling is tri-state: an oracle that could not rule reports
`containment-unverified`, not the delta's guilt.
- `upToDate` is no longer demoted when the full range is unavailable.
It is a fact about the ANCHOR, proven by the delta capture (or by
arithmetic for anchor-at-head), and the flow it serves — "No new
changes since last review", stop — consumes no plan at all. On a PR
whose base branch was deleted, every same-sha retry was fanning out
a degraded full review instead of stopping.
- Two fixtures declared old-side lines their bodies never emitted, so
a second hunk was swallowed as body content: the `sec` generator now
models a pure addition, and FULL_DIFF's header matches its body.
Both were latent (single-hunk uses only) and both are now covered by
a multi-hunk case.
- New pins: the no-newline marker mid-body, and the disk payload of
`publish()` in both the scoped-delta and the partition-rescue tests —
a write unpaired from the text the report describes is the same
mismatch class as the diffPath leak this PR shipped and fixed.
- The comment claiming the full-range flags are "skipped on a delta"
described the retired design; they read fullText on every round.
* fix(review): keep upToDate through a partition failure, and stop renaming deterministic refusals
Fourteen findings from the seventh dogfood round.
The Critical: the partition-failure demotion keyed on `effective`, which
upToDate rounds also carry, so `demote()` rebuilt the decision without
`upToDate` and the planless stamp renamed it — publishing "the anchor is
invalid" for an anchor that IS the head, and losing the stop branch on a
PR with zero new commits. Its stated rationale (Agent 7's welded --base
reading diffBase) cannot even apply there: an upToDate ruling never
carries one.
The other contract fix: the planless stamp fired on any refusal ending
with no plan, renaming two captured-but-degraded shapes into the class
SKILL retries. It now fires only when nothing was captured at all
(`fullText === null`); a partition failure keeps `partition-failed`,
which SKILL excludes from the same-sha retry precisely because the same
bytes re-fail identically.
Also from the round:
- The round-6 comment fix landed on the sibling clause; the one above
isCollapsedFromUpstream still claimed the retired "skipped on a delta".
- pr-context commanded the --since re-run unconditionally, contradicting
Step 1's admissibility gate; it now defers to it.
- An anchor resolving to the merge base re-captured the identical range;
it reuses the one already read.
- containmentRuling was computed twice on the refusal path.
- The comment claiming a raw-bytes write described the pre-restructure
code; the capture decodes to UTF-8 text now.
New pins for the round-6 fixes that had none: the oracle-LOST arm (base
resolved, its capture threw — the delta must not become the scope), the
tri-state reason on an unnameable path, the quotePath shape from the
oracle side, the disk payload on the containment-refusal path,
`diffPathAbsolute` in both directions, the no-newline marker landing
mid-body with counts still owed, and the pr-context routing sentence.
* perf(review): parse each diff once per containment ruling
The round-7 hoist removed the duplicate containmentRuling call but left
the duplicate parse inside it: the null check parsed both texts, then
hunksContainedIn parsed them again. Both now share sectionsContained
over already-parsed maps, so a ruling is two parses instead of four —
on the multi-megabyte diffs --since exists for, that is the difference
worth having. hunksContainedIn keeps its string signature; it is the
form the tests read.
* fix(review): let a refusal reason name only its cause, and unquote paths in the oracle
Fourteen suggestions from the eighth round — no Criticals this time, and
three of them were previous rounds' fixes that had not actually landed.
The contract change, flagged twice now: the planless stamp renamed
refusals into a label that meant both "why" and "no plan", so a
deterministic partition failure entered the retryable class and a
validity refusal invited re-running an anchor already ruled invalid.
The stamp is gone. A reason names its CAUSE; whether a plan exists is
`diffPath`, which the report already carries. SKILL.md's reason bullet
and same-sha retry list follow, and `full-range-unavailable` is retired.
The oracle now unquotes C-style paths itself (`unquoteCStylePath`, the
helper the chunk parser already uses). Real git quotes a path holding a
quote, a backslash or a control character even under
`core.quotePath=false`, so trusting the capture's config was never
enough — and the pin is now asserted where it is declared, since
deleting it fails nothing else.
Round 6's no-newline fixture is finally the shape it was meant to be:
my round-7 edit over-escaped and silently did not apply, so the marker
still arrived after both counts were spent and the in-hunk branch it
was written for never ran.
Also: an empty-string `diffBase` no longer silently drops Agent 7's
whole probe block, and SKILL's model-differs branch carries the same
`diffPath: null` caveat its siblings do.
New pins: the `!upToDate` exemption in the partition catch, the
anchor-at-head handler path, the merge-base dedupe shortcut, a
plan-derived field on the delta happy path, a multi-hunk OUTER range,
the base-untrusted/clamp order, the resolved-sha comparison for an
abbreviated head, the negative case for the routing tail, and the
pinned-config contract itself.
* refactor(review): build the containment oracle on the shared diff parser
Seven suggestions from the ninth round, again with no Criticals. The
class-level one is the structural change: three consecutive rounds each
found a fresh shape-tolerance defect in the hand-rolled unified-diff
grammar this PR added — count-less headers, trailing function context,
quoted rename headers, deletion junctions, hunk-less sections. The
oracle now reads its sections and hunks out of `parseDiff`, the parser
the chunk planner already trusts on these exact captures: it unquotes
paths, tracks hunk bodies, and knows the binary and rename shapes. A
ruling is set arithmetic over its output, and the private grammar is
gone. Paths that used to fail closed (a space, a quote, a mixed
quoted/unquoted rename) now rule normally; the remaining "could not
rule" state is a payload with no sections in it at all.
Also from the round:
- A probe ERROR was being reported as a verdict about the anchor:
`gitOpt` collapses every non-zero exit to null, so a 128 or a timeout
kill read as a definitive "not an ancestor" — a reason the recovery
flow treats as deterministic, so the anchor was never retried and the
round paid a full review for a transient fault. A new `gitProbe` keeps
the exit status, exit 1 stays the definitive no, and anything else
refuses as infrastructure.
- A value-less `--since` (yargs parses a bare flag to the empty string)
reported `unknown-commit`, asserting this history never held a sha
nobody supplied. It is now ignored with a line saying so.
New pins: the arm order that keeps `upToDate` through a lost oracle, the
`effective` clause that stops a refused anchor being relabelled
`partition-failed`, `diffPathAbsolute` nulling in the partition catch,
and the empty-string conjunct in Agent 7's base selection.
* fix(review): drop the containment slack, unpeel the existence probe, survive a bad flag and a bad write
Four findings from the tenth round, three of them Critical and all
verified against real git.
- The oracle's `e + 1` end-slack, documented for deletion junctions, was
applied to EVERY inner hunk, so a delta hunk ending one line past the
covering PR-diff hunk published as the review scope — a line GitHub
does not display, where an anchored comment 422s the whole review. The
measured trigger is ordinary: a revert adjacent to a kept change. The
slack is gone; shared deletions are covered at equality, since both
captures share the head tree.
- `cat-file -e <sha>^{commit}` answers 128, not 1, for a well-formed but
unknown sha, so the definitive-absent branch was dead code: every
unknown anchor was reported as a transient failure the recovery flow
retries forever, and `unknown-commit` was unreachable. The peel is
gone; "is it a commit" stays covered by `resolveCommit`, whose null
maps to `unknown-commit`.
- yargs turns `--no-since` into boolean `false` even for a string
option, which reached the hex test and then crashed on
`since.slice(…)` after the worktree existed and before any report was
written. A non-string anchor now falls through to the no-anchor path,
like the documented empty-string sibling.
- The extracted `publish()` left the diff-file write outside the
capture's try/catch, so an ENOSPC on a constrained runner killed the
command where it used to degrade to a diff-less report with disclosed
coverage. It returns false instead, and both call sites treat a failed
write as the capture failure it is — including the partition rescue,
where it must not be swallowed as a tiling failure.
Each is pinned, and the boundary test now asserts what its comment
always claimed. The suite's writeFileSync mock is reset per test for the
same reason its siblings are.
* fix(review): resolve commit-ness before ancestry, and give every probe the same three-way split
Twenty findings across rounds 10 and 11 — eight of round 10's were
missed on my side, because `reviewThreads(first: 100)` pages and this PR
now has more than 100 threads. The Critical and the shape of the rest:
- Three deterministic anchor classes were still landing in the retryable
`capture-failed`: an existing NON-commit object (a blob sha passes
`cat-file -e`, and asking `merge-base --is-ancestor` about it is an
error, not a "no"), an absent ABBREVIATED sha (git answers 128, not
1), and the 41-64-hex band a SHA-256 marker occupies against SHA-1
history. Commit-ness now resolves BEFORE ancestry, so the whole class
becomes what it is — an anchor this history holds no commit for — and
`cat-file`'s 128 is read as absence rather than as the surface failing.
- `resolveCommit` was the last probe folding every git failure into a
verdict about the anchor; it now splits three ways like its siblings.
- An uppercase-but-valid sha was refused before any probe ran, under a
reason asserting the history never held it, and echoed back cased so a
recovery flow re-submitted it forever. Anchors normalise at entry.
- The rescue path could announce "the round is a full review" and stamp
`partition-failed` when the rescue tiled but its write failed; that is
a capture fault and now says so.
- The planless stderr line blamed the capture on `partition-failed`
rounds, where both captures provably succeeded.
Docs corrected where they contradicted the code: the `commitExists`
signature (no `^{commit}` peel), the unconditional "a fetchFailed base
REFUSES the anchor" (only when a base resolved), the timeout's exit
status (a signal kill leaves `status: null`, not a high number), and the
"stays a valid patch" claim (the file is read, never applied).
New pins: the deletion junction in both directions, every one of the ten
pinned diff flags, an ancestry-only probe error, quote-bearing paths
keying apart, a multi-section delta, `emptyDiff` under ENOSPC, the
null-base clamp asserted on the CALL, and `gitProbe`'s exit-status
extraction against real git.
* test(review): observe the core.quotePath pin in the real-git capture
parseDiff unquotes C-quoted paths defensively, so every path assertion in
the integration suite reported sub中文.ts whether or not the capture pinned
core.quotePath=false. Deleting the pin from PINNED_DIFF_CONFIG left all
eight tests green while consumers that read the raw capture rather than the
parse tree would have started seeing the octal-escaped shape.
Assert both shapes against the raw text on either side of the pin, and set
core.quotePath=true in the hostile config rather than resting the control on
a git default that is free to change.
* fix(review): rule deletions by content, and pin the seams round 12 measured
The containment oracle compared NEW-side line ranges only, and a deleted
line occupies none of them: what survives a deletion hunk on the new side is
its context, which the covering hunk contains for free. So a delta that
removed lines the PR itself introduced after the merge base — the "undo per
feedback" round — was ruled contained, and the review scope became a diff
whose content GitHub displays on neither side, where one anchored comment
422s the whole Create Review call. Reproduced against real git: delta
`@@ -6,9 +6,6 @@` inside full `@@ -2,14 +2,14 @@`, ruled ok.
The two sides are comparable in different ways. Both captures end at the
same head tree, so new-side line numbers name the same lines and compare as
numbers; their old sides are the anchor and the merge base, so deletions
compare only by content. `-X` in the delta means X stood at the anchor and is
gone at head, so if X also stood at the base the PR must delete it too — and
its absence from the full capture is exactly the refusal.
Also from this round: name the planless cause instead of inferring it from
the refusal reason (a refused anchor whose full range then failed to tile
reported "no diff could be captured" moments after the capture succeeded);
drop `hunksContainedIn`, an exported wrapper with no production callers that
collapsed the two-fact ruling the refusal enum pays to keep; and correct the
`core.quotePath` rationale, which asserted a counterfactual this PR's own
parser refutes.
The rest is coverage for seams the round measured as unpinned — each added
test verified to kill the mutant that motivated it, and no fixture relaxed
to accommodate the new rule:
- probe exits split three ways (128 twice, a signalled `status: null`); the
shared shim can only produce 0 and 1
- commit-ness settled before ancestry, with ancestry given an error channel
- `not-an-ancestor` ruled ahead of `base-untrusted`
- the SHA-256 ceiling, from both the accepting and refusing sides
- the containment start boundary at one line, and each oracle input null
independently
- the rescue's write-failure and no-base branches, both previously unreachable
- Agent 7's `--base` welded through the real brief over the real report
- real-git `--is-ancestor` yes, no, and error
* fix(review): close four fail-open paths in the oracle, and pin round 13's seams
The containment oracle accepted an "undo per feedback" delta on four
distinct paths, each reproduced before it was fixed:
- Both captures reach the oracle already decoded as UTF-8, and that decode is
lossy. Two filenames differing only in an invalid byte share one U+FFFD map
key, so one file's hunks were judged against the other's ranges; two
byte-distinct deleted lines matched 1:1. Neither is detectable after the
decode, so the oracle now declines to rule rather than ruling on text it
knows is not the text git produced.
- Deletions compared by set membership, so a single `-X` in the PR's diff
cleared any number of `-X` lines in the delta. They are counted now: a
round deleting two identical lines where the PR deletes one is refused.
- A delta section with nothing comparable — a mode change, a pure rename —
carries no range and no deletion, so both containment loops iterated zero
times and it passed vacuously. That is right only when the covering
section is equally contentless; against one with hunks it is refused.
Agent 7's `--base` was interpolated unquoted into a fenced bash block behind
a `typeof === 'string'` check, which `abc123; touch /tmp/pwned` satisfies. It
takes SHA_RE now — the same predicate the anchor itself must pass — and falls
back to the merge base rather than throwing, because unlike the sibling
`host` guard a correct fallback exists here.
Two documentation defects the round found. The retry taxonomy forbade
retrying `partition-failed` because the partitioner re-fails on identical
bytes — true of the partitioner, false of a PLANLESS round, where the rescue
had no base and the re-run's base fetch is the component that changes. And
three fixture comments described `parseDiff` as closing hunks by their
declared counts; it closes them structurally at the next header, and the
mismatch was teaching future fixtures the wrong mechanics.
The rest is coverage for seams the round measured as unpinned, each added
test verified to kill the mutant that motivated it:
- gitProbe's `status: null` half against real git (PATH sabotage), and its
`out` half, whose trim `resolveCommit` depends on
- the timeout-kill arm of rev-parse and merge-base, not only cat-file
- an uppercase anchor through to acceptance, not only to refusal
- `collapsedFromUpstream` as a full-range fact on a delta-scoped round
- the deletion rule's per-file keying, the empty-plan invariant of a failed
rescue write, and the narration that distinguishes it from a tiling failure
- parseDiff's hunk-closing and no-newline-marker mechanics
- revert-guards for the SKILL.md sections and the pr-context tail this PR
ships, including the admissibility condition on the re-run instruction
* fix(review): match deletions inside the enclosing hunk, and widen the pins
The deletion budget was keyed per FILE, so a `-X` the PR's diff displays in
one hunk cleared a `-X` the delta performs thirty lines away in another — a
line displayed nowhere near where the delta deletes it, which is the same
422 the whole check exists to prevent. Locality was available all along:
both captures end at the same head tree, which is the fact the range check
already rests on. The parsed shape is now per hunk rather than flattened per
file, and each delta hunk's deletions are drawn only from the outer hunks
that enclose it. The empty-enclosure case subsumes the previous hunk-less
guard, so a delta that has hunks against a covering section that has none is
refused by the same arithmetic.
Agent 7's `--base` now shape-checks both sources. Only the anchor was
checked last round, while `mergeBaseSha` reaches the identical unquoted
interpolation on every non-incremental round — the common case — and the
plan is parsed with no field validation on that path. The test fixture's
`mergeBaseSha` was `abc123`, six hex characters, below git's own
abbreviation floor: a value `merge-base` cannot produce, so it is a full sha
now.
The retry taxonomy's planless exception was too broad. A planless
`partition-failed` splits on `mergeBaseSha`: null means the rescue never ran
for want of a base, so a re-run's base fetch is what changes; non-null means
both ranges were in hand and both refused to tile, which a re-run reproduces
exactly. The bullet names the field.
Coverage for the seams this round measured, each verified to kill the mutant
that motivated it:
- a deletion ending the hunk body with no trailing context, pinning the body
scan's trailing bound as well as its leading one
- one-sided lossy captures, which are what separate `containment-unverified`
from the reason that asserts a proven violation
- short deleted lines, where stripping two marker characters instead of one
transforms both captures identically and collapses `-a` onto `-`
- the provenance rule's discriminating condition and its fallback half
- the `cat-file` / `merge-base --is-ancestor` prohibitions, named in a test's
comment but asserted nowhere
- the ledger tail's antecedent and its validates-and-scopes clause
Also drops a dead ternary whose arms were byte-identical, in the helper that
builds the U+FFFD collision fixtures.
* fix(review): tie deletions to their junction, and stop scoping without a base
Three more ways an unchecked delta could publish as the review scope, each
reproduced before it was fixed.
Content alone never said WHERE. A `-dup` the PR displays near the top of a
file cleared a `-dup` the delta performs thirty lines down, at a junction the
PR's diff never touches — a single inner hunk against a single outer hunk,
budget spent exactly once, so no amount of counting closed it. Deletions are
now keyed by `content@junction`, the new-side cursor position where the line
stood. Junctions are comparable for the same reason ranges are: both captures
end at the same head tree. That is also what makes the file-wide budget safe
to hold across delta hunks, where rebuilding it per hunk had let one displayed
deletion clear one in each.
The base-free arm published the delta with neither the clamp nor the oracle
run — deliberately, on the reasoning that with no merge base there is no PR
diff to be contained in. The capture reasoning was right and the scope
reasoning was not: no diff to check against is the absence of proof, not
proof, and it was the one arm shipping a scope nothing had checked. It
degrades to `containment-unverified` now, which costs no review that existed
— a base-free round has no full range either, and already tells agents to run
their own diff.
The retry exception added last round keyed on a null `mergeBaseSha`, but that
null has two causes: a base fetch that failed, which a re-run repeats, and a
merge-base that found no common ancestor on unrelated history, which a re-run
reproduces exactly. It now reads `baseFetchFailed` as well.
The junction change required the fixtures to model real captures, which the
round independently asked for: `secDeleting` now places its deletions at a
stated junction, and three fixtures that declared hunk counts their bodies
could not produce are corrected. The DELTA/FULL pair gave one head commit two
different trees; it is one history now.
Coverage for the seams the round measured, each verified against its mutant:
- the cache-path opening of the incremental bullet, the only instruction that
makes `--since` fire at all, and the retry classification the recovery loop
acts on
- the body scan's leading bound, against the shape that injects a spurious
deletion from a section header
- the over-supplied deletion direction, where an equality rewrite refuses a
valid round under a reason that asserts a proven violation
- the no-newline marker's effect on junctions
- the ledger tail's command-naming and scoping clauses
* test(review): pin that an at-head anchor runs no delta capture
The handler test claimed to pin the `resolved === fetchedSha` arm "not just
the empty delta", but its fixture made the two indistinguishable: with the
arm removed the anchor resolves to the head, the handler captures
`f00df00d..f00df00d`, the mock answers empty, and the empty-delta arm sets
the identical `upToDate` — report and written diff both byte-identical.
Assert the capture ranges instead. Deciding at-head before any capture runs
is what removes the redundant `git diff`, and that is now what the test
measures: under the mutant the ranges come back as the merge-base range plus
`f00df00df00d..f00df00df00d`.
|
||
|
|
c48809341b
|
feat(external-context): Add provider extension profile (#9068)
* feat(external-context): Add provider extension profile Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(external-context): Harden provider extension profile Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(external-context): Harden provider profile bounds Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(external-context): validate provider profile boundaries Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(external-context): harden provider extension example Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(external-context): Use forward proxy for HTTP providers Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
2af4d3919a
|
test(review): realpath the skill-parity fixture root (#9269)
bundleStalenessNotices resolves the entry with realpathSync before deriving the tree, and the mock's fault injection matches by exact path. On a platform where tmpdir() is a symlink (macOS: /var -> /private/var) the unresolved fixture lived in a different namespace from every path the walk produces, so the read-fault stage never fired and its notice silently dropped out of the six-line pin — the suite failed with 'expected length 6 but got 5' at the base commit on such machines. Building the fixture under the realpath'd root puts every derived path in the same canonical namespace as the walk. |
||
|
|
8a34d3eee1
|
test(web-shell): pin silent failure of background artifact refreshes (#7427) (#9227)
* test(web-shell): pin silent failure of background artifact refreshes (#7427) The toast-spam behavior reported in #7427 no longer exists on main — loadArtifacts carries no notice dispatch and the hook swallows background-refresh failures, keeping the last-good artifacts. What was missing is a regression pin: add one that fails a background refresh and asserts last-good artifacts survive, loading clears, no error surfaces, and the next refresh recovers. Mutation-verified: clearing artifacts in the catch turns it red. * test(web-shell): cover artifact refresh triggers * test(web-shell): tighten artifact refresh assertions * test(web-shell): settle artifact-refresh mocks via deferred awaits (#7427) The two regression tests added here flushed their mocked refreshes with a single microtask, so under full-suite parallel load the hook's refresh continuation intermittently missed the React commit and the last-good assertions saw artifacts === [] — a signature indistinguishable from a real #7427 regression. Model every mocked load as a deferred and resolve/reject + await it inside act, the shape the file's pre-existing tests already use (review round, 7 fragile flush sites). Also fold in two review pins while the tests are being rewritten: - R2-2: the superseded-failure test now rejects the stale load while the superseding load is still in flight and asserts loading stays true — the requestId guard in the finally cleanup becomes load-bearing (mutation-verified: dropping the guard fails the test). - add a waiting -> idle settling-trigger test so the prompt guard cannot be specialized to 'streaming' ('waiting' is a real prompt status). * test(web-shell): pin the version bookkeeping and owner-guard halves (#7427) - Non-monotonic artifactsVersion sequence (1->2->1 from a non-zero start) with exact loadArtifacts call counts: killing the previous-value bookkeeping silently skips the refresh that returns to a previously-seen version (stale artifacts panel, suite green) — measured mutant. - Owner-flip supersede variant: the provider flips isCurrent() the instant the session switches, before a re-render; an in-flight load resolving in that window passes the requestId half and only the !owner.isCurrent() half of the guard stops it from painting the previous session's artifacts — deleting that half left all prior tests green (measured). * test(web-shell): pin superseded artifact successes --------- Co-authored-by: yiliang114 <yiliang114@users.noreply.github.com> |
||
|
|
6970c76114
|
test(review): close confirmed pin gaps from #9194 (batch 1) (#9225)
* test(review): close confirmed pin gaps from #9194 (batch 1) Pin the auth<gh ordering half (issue-context/comment-body/fetch-diff), the issue-context write-target path, the full 'NOT in the closing set' extras header wording, and the Number.isInteger guard halves for fractional pr_number/id/--pr (mutation-verified). Audited the rest of the checklist: whitespace-only --host rejection, fetch-diff non-integer case, comment-body write path, and pr-context host-routing/omitted-host pins are already covered on main. Remaining #9194 items deferred to follow-up batches. * test(review): pin meta auth ordering * test(review): complete command pin gaps * test(review): pin omitted host resets --------- Co-authored-by: yiliang114 <yiliang114@users.noreply.github.com> |
||
|
|
13472eb721
|
fix(review): keep the presubmit overlap list out of the canonical findings artifact (#9268)
Step 7 told the orchestrator to write the flat {path, line, id?} overlap list for presubmit --new-findings to the very file that holds the canonical findings artifact, replacing it with a flat shadow before Step 8 archives it. The list is presubmit input, not the artifact — give it its own file (qwen-review-{target}-new-findings.json), still covered by the Step 9 cleanup glob.
|
||
|
|
c031cc6279
|
fix(ci): self-heal failed checkouts on the reused review runners (#9220)
* fix(ci): self-heal failed checkouts on the reused review runners A checkout failure on the self-hosted review pool was terminal: either a transient network drop mid-fetch (curl 92 / early EOF), or a corrupt persisted workspace whose refs claim objects missing from its object store, after which every fetch dies in negotiation with 'remote did not send all necessary objects'. ecs-qwen-runner-64c-23 stayed in that state for two days (2026-08-13..15), failing seven review jobs on the same missing SHAs. Make the first checkout continue-on-error; on failure wipe the whole workspace (not just .git) and retry the identical checkout once. The workspace is disposable — later steps reinstall deps and tools. * fix(ci): heal with the pool wipe idiom and pin the checkout guardrails (#9220) * fix(ci): pin the heal chain's sudo leg, path guard, and survivor signal Addresses the 16:40 review round on the checkout self-heal: - The wipe-failure test leaned on the real sudo, so it covered a different branch per lane; replace it with a PATH-stubbed sudo that forces both legs to fail hermetically, and pin the survivors left in place plus their oncall-visible warning. - The '|| sudo -n find' escalation leg survived deletion mutants: add a stub-sudo test proving the leg actually runs when user-mode find fails (leg-deletion and '||'->'&&' mutants both verified red). - Reuse the triage idiom's suspicious-path guard before wiping. - Count post-wipe survivors and warn with the count — triage exits 1 here, but the heal chain must stay alive for the retry. - Disclose in the step comment that the sudo leg only helps pool members with passwordless sudo. * fix(ci): close the heal guard's trailing-slash hole and name wipe survivors (#9220) * fix(ci): canonicalize the heal guard's path match and allowlist the runner workspace (#9220) * fix(ci): strip the heal allowlist root's trailing slashes and pin the guard layers (#9220) * fix(ci): canonicalize the heal lock fixture and pin the WS strip loop (#9220) * fix(ci): keep the checkout-heal suite green on a BSD userland Addresses the 09:38 review round: three of the new tests assume the wipe script's `realpath -m` canonicalization actually ran, and `-m` is a GNU coreutils extension — Darwin ships FreeBSD's `realpath [-q]`, exits 1 on it, and the script's `|| printf` fallback silently keeps the raw path. The production script is unaffected (the review pool is Linux-only), but this suite is excluded on win32 alone, so it also runs on the macOS lane, where the assertions are red for a defect that cannot exist there. - Probe the host for `realpath -m` and skip the canonicalization test when it is absent, rather than skipping on `platform === 'darwin'`: the probe keeps the coverage on a Mac with coreutils on PATH and still skips on any other non-GNU userland. Mutation-checked on a GNU host — deleting the canonicalization line still turns the test red. - Spell both halves of the allowlist comparison the same way in the lock fixture: it resolved its workspace with realpathSync while the runner-workspace root stayed raw, so on a symlinked tmpdir (macOS /var -> /private/var) the two sat on opposite sides of the link, the guard refused, and the wipe helper threw before any assertion ran. Canonicalizing the root keeps both tests running everywhere instead of leaning on the GNU-only flag to reconcile them. - Record in the step comment that `-m` is GNU-only and that off-GNU the guard degrades to the strip loop and the allowlist. Verified by simulating a BSD userland (a PATH-fronted realpath that rejects -m): the suite goes from 1 failed / 12 passed to 12 passed on the CI lane's environment, and from 3 failed / 10 passed to 12 passed with a symlinked TMPDIR, matching the two failure shapes reported. --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
df99f05e51
|
feat(review): run-session ledger and cross-session agent evidence (#9091)
* feat(review): run-session ledger and cross-session agent evidence Groundwork for resuming an interrupted review run. fetch-pr now records its session id in a run ledger beside the prompt records and stamps the plan with a sha256 of the captured diff bytes; a new readRunTranscripts reads the harness transcripts of every session the ledger names (the current session's contract is unchanged), so coverage, retirement, the layer-audit gate and the cost ledger can credit an earlier attempt's certified work. Coverage counts such agents as recoveredAgents and discloses the continuity; the cost ledger folds the earlier sessions' main loop and agents into the run's totals and reports priorSessions. A run that never resumes sees no behavior change: with no ledger entries every reader reduces to its previous single-session read. Fabricated ledger entries grant nothing — they only name directories under the harness's own subagents tree, and credit still requires the existing content-shaped pairing (verbatim-delivered prompt, opened brief, diff reads). * fix(review): address review feedback on the session ledger Ledger.main drops its stale | null (computeLedger throws before folding when the current chat holds no above-floor record, so the renderLedger guard was dead code); recordResume/recordRestart gain dedup guards so a caller-side retry cannot double-count toward the resume cap or fake a second restart; fetch-pr's ledger append is now wired-tested (called with the plan path, after the plan write); and the layer-audit gate's real reader gets its own prior-session tests, including the discriminating partial-walk shape that separates invisible from credited. * fix(review): keep the run epoch stable and the continuity note non-capping Two Criticals from the automatic review. repo-context's enrichment rewrite advanced the plan's mtime — the run epoch every fence keys on — which orphaned the session-ledger entry fetch-pr had appended an orchestrator turn earlier: on a resume the prior attempt's transcripts were invisible and the feature silently re-ran everything in the primary medium/high flow. The rewrite now restores the plan's mtime (enrichment is not a re-capture), pinned by a test that backdates the plan and asserts the mtime survives the command. The continuity disclosure also moved off the capping disclose() channel: compose-review folds every disclosure into the unreviewed-dimension cap and the "Not reviewed:" rendering, so any resumed run that recovered work was permanently downgraded to COMMENT and its reused work called not-reviewed — unrepairably, since the prior records never leave the ledger. Coverage now only counts recoveredAgents, and compose-review renders its own continuity block beside the other disclosed-but-not-capping notes (deferred lint, test-plan rulings), on every verdict including Approve. A new compose test pins the clean resumed run at APPROVE with the note and without the partial-review opener. * fix(review): contain, bound and supersede the prior-session reads Round-2 blockers on the ledger PR. The currentDirOptional escape is now narrow on both axes — only ENOENT (the error's cause travels with it) and only when the run ledger actually names prior sessions — and it is passed at every reader that runs before the resumed session launches anything: coverageFromTranscripts, verificationGaps, the retirement scheduler and the layer-audit gate, which without it failed OPEN on the layers a prior attempt never walked. Prior-session directories now come from one guarded accessor that skips a symlinked subagents/<id>, so a planted link cannot feed foreign transcripts to the transcript union or to the cost ledger, which reads file content with no certification step. The ledger also clamps each prior chat to the moment the next attempt began (an interrupted CLI session that kept serving unrelated turns was billed as review cost), discloses an unreadable prior agent dir instead of silently flooring it, and sums each session's own span for wall time rather than the envelope across the dead gap. Coverage's Uncoverable declaration takes the supersession guard its sibling flags already had: a stale declaration deleted live coverage post-loop and order-independently, so no relaunch could ever clear the cap; a record that declared a chunk unreachable is also no longer counted as recovered. The ledger read path deduplicates and applies the session-id charset gate to the resume marker too. * docs(review): say what the recoveredAgents bar actually is It is a strict subset of the live credit bars, not "the same bar": no drift rescues, because the count reports reuse and caps nothing, so it should under-claim rather than vouch for a delivery the pairing could not fully confirm. * fix(review): bill a resumed run once, clamp its agents, and say what the pairing proves Five blockers from the round-3 review of the ledger PR. The cost ledger pushed TWO overlapping spans for a prior session — its chat window and the agent window nested inside it — so a resumed run's wall time double-counted the nested minutes; it now folds one span per session from the union. Prior-session agent transcripts were read with only the floor, so unrelated subagents launched in the interrupted CLI session after the review died billed to the review; readAgentDir now takes the same ceiling the chat already had. The two new chmod-000 fault tests get the repo's platform/root guard: on Windows chmod only toggles the read-only attribute and readdir still succeeds, which would have turned the test_windows merge-queue job red on this PR's own tests. And the module header's claim that the content-shaped pairing is something "fabrication cannot satisfy" was refuted by probe: an actor who can write into the harness tree can read a recorded prompt back and plant a matching transcript. The prose now states the guarantee that holds — it defeats an orchestrator fabricating its REPORT of the run, not an actor with write access to the tree, and that bar is the same one the current session's records have always cleared. Also from the same round: the ledger read paths refuse anything that is not a regular file (a planted FIFO blocked readFileSync forever while the write side was already noFollow-hardened), the epoch fence gained its upper half (a future-dated entry outlived every later rewrite), endsAtMs is derived in time order rather than file order so the cost clamp cannot invert, transcriptDirsForRun is gone (no production caller), and the tests gain the missing verdict assertion, the two zero-launch cases that actually exercise currentDirOptional at the retirement and layer-audit call sites, a findIndex-miss-proof ordering check, and a compose fixture that is no longer re-contaminated by base()'s eagerly evaluated default. * fix(review): refuse mid-flight credit, key the fence exactly, fold the ids A batch of findings that had gone untriaged (their threads were opened under the author account, so a reviewer-side filter had hidden them). A prior attempt's agent that died mid-flight — verbatim prompt, a logged diff read, no return — passed every coverage guard and marked its chunk covered, so the resumed run skipped the relaunch and the chunk's findings never existed anywhere. A prior-session record with an empty final text now credits nothing, in the coverage walk and in the recovered count alike. The current session keeps today's semantics: an empty return there is an agent still running, which the idle checks own. Session ids become PATH segments, so `s1` and `S1` are one directory on APFS and Windows: a case-variant ledger entry passed the string exclusion and re-read the current session as a prior one — every record twice, `recoveredAgents` minted on a run that never resumed, and the current chat folded into the prior totals. The exclusion and the dedup are now case-insensitive. The fresh-run fence was inexact by its own slack: a previous run that appended within it survived this run's plan write. Each entry now records the plan mtime it saw, and a reader keeps only entries that saw THIS plan — exact by construction, with the window as the fallback for entries written before the field existed. Also: the run-epoch fence had a verbatim private copy in three modules and now lives once in prompt-record.ts, and the prior-directory read no longer re-implements the files-to-records pipeline — one `recordsIn`, so a future record-level filter cannot apply to live evidence while bypassing recovered evidence. * fix(review): gate prior evidence on an authorized resume, and bound what it reads The round's central finding: the ledger is an address book, not permission. Any session pointing at an old plan could union the ledgered attempts' transcripts and inherit their coverage — after head drift, stale evidence could certify code nobody reviewed. Reading prior evidence now requires this session's own entry in the resume marker, which `fetch-pr --resume` writes only after every probe passed. One gate, in the accessor every reader goes through. With it, several narrower holes close too. A transcript is checked against the session that owns its directory, so a record COPIED into another attempt's directory cannot earn that attempt's credit. Each prior attempt's evidence is bounded above by the moment the next attempt began, so a session that kept running after the resume took over is no longer credited to the review. The exact fresh-run fence loses its legacy fallback — the field ships in the same change as the ledger, so there are no older files to be lenient toward, and the fallback was the only way a previous run's entry could survive a plan rewrite. And both ledger files are bounded in bytes and entries before they are parsed: a planted huge or duplicated ledger could otherwise stall every command that touches bookkeeping, or spend the resume cap. The fixtures now build their state with the real writers instead of hand-written JSON, which is why several of them changed shape: a hand-written ledger has no plan stamp, and a re-homed transcript that keeps its old session id is exactly the misplaced shape production now refuses. * fix(review): drop unfinished prior records at the source, and not count superseded ones Two halves of the same principle. A prior attempt's agent that never returned is excluded from the record set EVERY gate reads, not just from the coverage walk — the same record would otherwise still satisfy roster matching and the Step 4/5 delivery floor, and a gate reading a different evidence set than its siblings is how a review certifies work nobody did. And `recoveredAgents` no longer counts a prior record whose obligation a current relaunch already satisfied: the count is what the continuity note reports, so claiming recovery for work this run re-did would misdescribe it. The compose fixture that hid this is fixed too — `base()`'s literal evaluates its `coveredPlan()` default even when the caller overrides `planPath`, which re-created the current-session record the test had just re-homed. * fix(review): bill each attempt from its own start, and require delivery for a receipt The cost floor was the plan's mtime for every stream, so a review that began inside an existing CLI session billed that session's earlier, unrelated turns — and on a resumed run each prior attempt was floored the same way. Each stream is now floored at the moment its session became a review attempt, from its own ledger entry, and a stream that exists but cannot be read is counted and disclosed rather than silently skipped: a lower total must not be presented as a complete one. The layer-audit gate now requires a receipt-contributing auditor to have been launched with the CLI's own recorded prompt and to have opened the brief it points at. Territory alone let a compliant sibling satisfy the floor while a hand-written auditor supplied the claims. repo-context skips its enrichment write entirely when the serialized plan is unchanged — the common resumed case, where the commit-then-restore window simply cannot open — and when it does write, a failure to restore the plan's timestamp is reported instead of silently advancing the run epoch out from under this run's own evidence. Tests: verificationGaps is pinned directly on the zero-launch continuation (evidence only in a prior session, no current transcript dir), the resumed-run ledger line is asserted in both numbers and by its absence, and the compose auditor fixture now delivers the recorded prompt it always claimed to. * fix(review): close the certification, epoch and supersession gaps the audit found Five behavioural defects, each mutation-proven and each now pinned by a probe that reddens when the fix is reverted. The Step 4/5 gate read run-scoped evidence WITHOUT the dead-record filter its coverage sibling applies. Delivery is checked as recorded prompt plus opened brief and never consults the return, so an interrupted attempt's verifier that opened its brief and died satisfied the floor — a verification nobody performed, certified. The filter now lives in one function both gates call: a gate reading a different evidence set than its siblings is how a review certifies work nobody did, and that is exactly what the split allowed. The plan-epoch restore passed `Date` objects to `utimesSync`. A `Date` carries whole milliseconds; APFS and ext4 keep nanoseconds. The restored mtime was therefore CLOSE to the original and not equal to it, the exact `planMtimeMs === planMtime` fence read it as a different plan, and every session entry was dropped — the resume ledger silently emptied on the filesystems this actually runs on. Restored from float seconds now, and the verification compares with a millisecond of tolerance instead of exact float equality, which was firing on every content-changing enrichment (28 leaked WARNINGs in the suite, and not one test noticed). The recovery count's supersession check iterated ALL records, so two prior records for one obligation — a whiff-relaunch inside the interrupted attempt — superseded each other and both vanished from the count while coverage still credited their chunk. Narrowed to current-session records: the count answers "what did this run reuse", so only a relaunch HERE supersedes. Two test-only defects in the same pass. The check-coverage test titled as accepting prior-session Step 4/5 evidence contained none: with no such records both steps fail as not-built and merge into one gap whose subject is the combined `'verification and reverse audit'`, which equals neither name the assertions excluded — it passed on a review where nothing was verified. It now builds real Step 4/5 fixtures and asserts no gaps at all. The current-session billing floor had no discriminating test (reverting it kept all 56 green), and the APPROVE-path continuity separator had none either (dropping the clause glued the note onto the verdict with a single space, all 230 still green). Also: three JSDoc blocks that mid-file insertions had stranded on the wrong symbols, and a paragraph promising a window fallback for entries without `planMtimeMs` that the code stopped honouring when the fallback was removed — two contradictory contracts in one file, with the doc's version being the one that loses a resume its evidence. * fix(review): compare the plan-mtime fence within a millisecond, not exactly Linux CI caught what macOS hid. Restoring the plan's mtime through `utimesSync` costs a unit in the last place on ext4 — 1786717283911.999 goes back as 1786717283911.998 — because `mtimeMs` is a double over a nanosecond clock and `utimesSync` takes seconds as a double. Restoring from float seconds rather than a `Date` narrowed the drift from whole milliseconds to a fraction of a microsecond, but the ledger compared EXACTLY, so the run's own plan still read as a different one and every session entry was still dropped. The resume ledger emptied itself on the filesystem the CI runs on. The fence now allows a millisecond. That is orders of magnitude above the representation noise and orders of magnitude below the thing it must still separate: a fresh capture of the same PR rewrites the plan seconds or minutes later, never inside the same millisecond. The two epoch tests compared exactly for the same reason and are now on the same tolerance — and the sub-millisecond one no longer infers the property from a timestamp at all: it writes the ledger entry `fetch-pr` would have written, runs the enrichment, and asserts the entry is still visible to the continuation. That is the consequence the timestamp was standing in for, and it cannot pass on a filesystem whose round trip loses the fraction. * feat(review): expose the ledger's session count without the evidence gate The resume cap reads two counters so that deleting one cannot reset it. The second counter never worked: it came from `priorSessionIds`, which is gated on the calling session already appearing in the resume marker, and that entry is written only after a ruling passes — so at ruling time the ledger term was structurally zero, and deleting `resume.json` reset the cap the ledger was supposed to backstop. The gate protects EVIDENCE — it stops a session that was never granted a resume from reading another attempt's transcripts. A count is not evidence: it says how many times this review has been picked up and nothing about what any attempt did. So the count gets its own ungated accessor, running through the same `readSessions` fences as everything else, and the cap can be wired to it. * fix(review): verify against the CURRENT findings digest, and close the owed pins R1-5, deferred two rounds ago and now closed: `verificationGaps` took the best delivery across every `verify--<digest>` key ever recorded, so a verifier that succeeded against an EARLIER findings list satisfied the floor for a list it never opened. The behaviour predates the resume work, but widening the record set to prior sessions is what made it reachable in practice, so it lands here rather than as the follow-up it was parked as. The keys are narrowed to the newest digest before ranking, dated by the digest's own findings file — shard keys of one digest land within a moment of each other, a previous list's are a round older — and keys with no findings file stay in, because they cannot be dated and also cannot reach `ok`, so they only ever make the verdict stricter. Two invariants acknowledged in earlier threads now have their probes. The empty-current-chat refusal deliberately precedes the prior-session fold — prior events must not vouch for a broken current recorder — and every refusal test predated the ledger, so the faithful mutation (prior sessions excuse the emptiness) shipped green; it now reddens a probe with a healthy prior chat and an empty current one. The rendered "totals include N earlier session(s)" line is asserted on the rendered text, where it can be deleted or crash at print time with every return-value test still green. And the fixture drift named in review is repaired at both call sites: the `runLedger` helper with a dead first parameter, and the layer-audit fixture hardcoding the derived `plan-prompts` name instead of asking `promptRecordDir`. * fix(review): close the round-4 blockers a paginated sweep surfaced Nine Criticals, missed for two rounds because the triage sweep read the first GraphQL page of review threads and this PR crossed one hundred — the finding list below is what a paginated read surfaced. A RETURN is now a fact, not an inference from non-empty text. `finalText` keeps the last non-empty assistant message, which includes progress narrated between tool calls — an agent that said "reading the diff now…" and died carried plausible text that certified coverage and the Step 4/5 floor. `parseTranscript` marks text with tool traffic after it as progress, and every certification consumer (liveRecords, certifies, all four supersession predicates) requires the record to have returned. That last group also closes the probe-proven fail-open where an unreturned verbatim relaunch suppressed an honest `Uncoverable:` declaration and earned the chunk off the told-range presumption, let two honest declarations annihilate into `missingChunks`, and silenced a prior attempt's `Budget gap:` disclosure as a "genuine repair". The layer-audit gate no longer treats NAMING the brief as reading it — a grep whose args contain the path cleared `delivered()` while the auditor never opened its instructions — and its record read now takes the run-epoch fence `readRecordedPrompts` documents as mandatory for history readers: without it, a dead attempt's records beside the stable plan path let a hand-launched stale prompt corroborate a run whose builder never emitted an auditor, a fail-open on the gate's own withhold-only invariant. Retirement receipts classify only from auditors that READ the cumulative findings list their prompt points at: the comparison against known findings is the audit's method, and two skipping receipts retired a chunk on a comparison nobody made. Transcript ownership is now checked for the current session, not only prior ones — a foreign-stamped file planted under the current directory was trusted as current evidence, `since` blind to it (a copy gets a fresh mtime) and the prompt pairing deterministic. And the session-directory lookup applies the harness's own filename sanitizer: the harness writes `subagents/<sanitized>` while the ledger's charset admits dots, so a dotted id read a path that does not exist and every reader silently saw nothing. `appendRunSession` refuses to write an entry when the plan cannot be stat'ed — `readSessions` hard-requires the field, so the entry was a guaranteed-dead write that silently lost the id on the next append's rewrite. `repo-context` captures the plan's identity before the providers run and refuses to write if it moved — a concurrent capture otherwise had this run's stale contents restored under the other run's epoch, whose ledger then passed an exact fence against a plan it never described. Every fix is pinned, and each pin was mutation-verified against the exact regression the finding names. * fix(review): validate ledger entries before the cap consumes them R5-1: all three bounded reads sliced the untrusted array BEFORE validating it, so sixty-four malformed entries at the front consumed the whole cap and hid every real entry behind them — `sessionEntryCount` read zero and the resume cap reset, which is precisely the attack the count exists to survive; the marker's resumes and restarts had the same shape, resetting the once-per-review restart bound the same way. Filter first, cap the survivors. The validation cost the original order was avoiding is bounded by MAX_LEDGER_BYTES — the file cannot hold enough entries for the cheap field checks to matter — while the cap's actual job, bounding the per-entry directory reads consumers pay, is done by capping what is RETURNED, and that stands either way. Pinned from both sides: seventy junk entries ahead of one valid entry count as one, and seventy valid entries still cap at sixty-four. * fix(review): work through the round-3-to-6 suggestion backlog The behavioural fixes. Session identity now folds on the PATH the id becomes — sanitized and lowercased — everywhere at once: dedup, the current-session exclusion, the marker's dedup, and both write-side duplicate checks. Folding on the raw id left every alias the filesystem or the harness sanitizer collapses (case variants, trailing dots, sanitized '.') open as a second session wearing the first one's evidence. Read-time dedup keeps the EARLIEST duplicate rather than the first in file order, which handed an out-of-order hand-written duplicate the session's identity and erased the window between the real start and itself from billing. The resume marker takes the same exact plan fence as the session ledger — the window alone is inexact by its own slack, and a previous run's resumes surviving a rewrite arrive with the cap already spent — and restarts dedupe for the same reason resumes always did. Marker writes stamp the plan mtime and refuse when the plan cannot be stat'ed, like the ledger's own dead-write refusal. Appends refuse to rewrite over a ledger that EXISTS as a regular file but could not be read: rewriting from the empty fallback on a transient fault erased every previously recorded entry, and the guard is keyed on the file's type so the pinned self-healing over planted symlinks stands. The layer-audit gate's record fence is the STRICT plan mtime, not the slacked epoch — record mtimes and the plan's come off the same clock, and the slack would re-admit a dead attempt's records written in the two seconds before a re-capture. The epoch JSDoc now says which artifacts key on which fence instead of claiming one definition covers all. The cost refusal message names the boundary that actually filtered (this attempt's start, on a resumed run). repo-context's three stale comments now match the shipped tolerance fence, and the run-ledger module header claims containment only for the certifying readers — cost is accounting, and the honest claim stops there. Twenty-odd guarantees that could be deleted with the suite green are now pinned, each mutation-verified: the byte and entry caps, the per-session authorization property, currentSessionEntry at all, the charset gate at read WITH a valid fence, both write-side guards observed through the raw file, the marker's schemaVersion/case-dedup/noFollow/swallow properties, the symmetric plan fence, the earliest-duplicate rule, the prior-side ownership and window clamps, the byte-vs-string diff hash (an invalid-UTF-8 buffer, which no string fixture can express), the whole-diff recovery branch (exact count), the key-shaped recovery count, the Step 4/5 refusal by name, the `contributed > 0` guard, two-prior-session folding, the handoff boundary operators, the gate's territory clause and identity filter on fixtures that pass every OTHER clause, and repo-context's write-skip through the real serializer. * fix(review): close round 7 on the ledger — cap order, prior prefix, digest keys Three probe-proven blockers, all in code earlier rounds added. The bounded read capped in FILE order before sorting and deduplicating, so 64 valid hand-written duplicates at the front evicted every genuine entry — and the next append rewrote the file from the filtered survivors, laundering the plant permanently; with 64 distinct entries the just-appended current entry (always file-last) was the one dropped, nulling the cost floor. The pipeline is now sort → dedup → cap, each step defeating the payload the next one cannot. `priorSessionEntries` classified every non-current entry as prior, so a twice-resumed run read as the MIDDLE attempt received its own successor as a "prior session" with an unbounded window — its later unrelated activity folded into this attempt's bill, its records entered the evidence pool with no ceiling. Prior now means the PREFIX strictly before this session's own entry, and the last prior's window closes at this session's start. `currentDigestKeys` kept undatable verify keys on the premise that they cannot reach ok — false for the write-failure fallback, whose inlined list leaves no findings file and no pointer, making the findings-read floor vacuously true. A stale pointerless verifier could vouch for a newer dated list no verifier opened. Undatable keys are now dropped once any dated key exists; with no dated key at all they are the only evidence and stay. `sessionEntryCount` gains an exclude-current option for the cap's ledger term (consumed by the stack's fetch-pr): counting the session's own entry in either term refuses a same-session retry of the last permitted resume, whose fresh fall-through then destroys the very state being resumed. * fix(review): work through the round-7 suggestions on the ledger PR Code: the recovery count's uncoverable veto is chunk-scoped like the walk's (a recovered whole-diff auditor legitimately QUOTES the declarations it audited); the layer gate delivers only against reverse-audit records (a concatenated launch verbatim-matched a sibling role's record and satisfied the brief bar with the sibling's brief); retirement extracts the findings pointer ONCE from the raw prompt (the re-extraction from trim-normalized lines defeated the anchors that reject indented quotations) and the orphaned memo-contract JSDoc is back on findingsListFor; the endsAtMs doc and the diffSha256 comment now say what the code does at this commit. Fixtures: the flake family is closed — the three suites' prior-session helpers backdate their files ten seconds, so a CI stall between the ledger stamp and the write can no longer fence the fixtures out through the until clamp. And a dozen probes that could not discriminate the guard they name now can: the over-budget ledger carries a valid entry, the symlinked target carries a fenced entry, the marker's charset gates are pinned on both sides with valid fences, the alias fold's entries sit inside the window, the marker's plan fence survives a 3s nudge instead of dying on the epoch window, resumeAuthorized folds case, the stale gate record sits INSIDE the would-be slack, the identity anchor refuses a substring mention, a delivered auditor with zero diff reads stays refused, the digest narrowing accepts the compliant current digest, per-record granularity is pinned with a mixed prior session, the prior-side floors and the per-session ceiling have events inside their discriminating windows, the plan-swap abort fires on the restored-mtime rename shape, and the epoch test asserts the rewrite's content. * fix(review): round-9 blockers — single-read ledger writers, lifecycle-aware returns, floor narrowing run-ledger: the clobber guards decided from a SECOND read, so a transient fault clearing between the two reads let an append rewrite the whole ledger from the empty fallback; all three writers now classify the occupant once and decide everything from that one read, with the plan stat shared between the fence and the new entry. Plant shapes that can never be legitimate state — a directory (EISDIR on every rename, swallowed forever), an oversize regular file (frozen as "state to preserve" for the life of the plan) — are healed by the writers instead of freezing recording. The entry cap keeps the NEWEST end, so a backdated flood of distinct ids truncates itself rather than evicting the genuine entries and laundering the eviction on the next append. transcripts: `returned` now consults the harness's own lifecycle record — the `agent-<id>.meta.json` sidecar — so an agent killed after a text flush (which ends identically to a completed one in transcript content) no longer certifies; thought parts are excluded from the return text, so a thinking-mode agent killed between ROUND_TEXT and its tool calls does not hand its internal reasoning downstream as a verdict; and a transcript whose records carry two different session stamps is rejected whole — the grafted-tail shape that defeated the first-stamp ownership check. coverage: `currentDigestKeys` dates a findings-file-less key by its always-present prompt record instead of dropping it, closing the mirror case where the CURRENT digest's inlined-fallback keys were dropped and a previous round's verifier vouched for a list nobody opened; the same narrowing now applies to the Step 5 reverse-audit floor; and the Uncoverable supersession guard excludes records that themselves declare the same chunk, so two honest declarers no longer annihilate each other into a permanently unreviewable `missingChunks` loop. layer-audit-gate / retirement: both readers now require `returned` before consuming receipts — a died-mid-flight auditor's narration can carry receipt forms — and the gate's `delivered()` refuses a transcript that verbatim-matches more than one reverse-audit record (the concatenated-launch shape whose union territory corroborated layers no walk touched). Retirement's findings-read floor moves INTO the dry branch of `classifyReturn`, so a filed yield from an auditor that skipped the list read keeps its chunk hot instead of vanishing before classification. repo-context: the enriched plan is committed by a temp file that is stamped with the anchor's times BEFORE the rename and identity-checked against the anchor immediately before it — there is no longer an instant where the plan path carries an advanced epoch (the kill-window that permanently orphaned same-run evidence), and a concurrent capture landing during the read+compare+write window is refused instead of silently overwritten. Every fix carries a probe pinned by mutation: reverting each guard in isolation reddens at least one new test. * test(review): give the inode probe an imposter that cannot recycle the inode ext4 reuses a just-freed inode number immediately, so the delete-then-create fixture came back as the same (ino, mtime) identity — indistinguishable by design, and red only on Linux. The imposter is now created while the original exists and renamed over it, so its inode is distinct on every filesystem. |
||
|
|
62014c0188
|
fix(serve): Bound ACP HTTP pre-attach buffers by bytes (#9007)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
Security Checks / Dependency CVE audit (push) Waiting to run
Security Checks / Secret scan (TruffleHog) (push) Waiting to run
* fix(acp): Account for JSON string escaping in response budgets Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(serve): Bound ACP HTTP pre-attach buffers by bytes Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(serve): Preserve ACP pre-attach stream scope Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(serve): Expose ACP guard failures by workspace Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#9007) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#9007) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#9007) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(serve): Preserve ambiguous WebSocket deliveries Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(serve): Report failed ACP response delivery Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#9007) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#9007) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#9007) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#9007) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#9007) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(cli): make websocket teardown logging safe Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * test(serve): align ACP fork fixtures after rebase Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
b744248656
|
fix(ci): keep a fallback comment when the PR review runner dies (#9255)
* fix(ci): keep a fallback comment when the PR review runner dies A review job that dies abnormally never reaches its in-job fallback comment step: the runner worker crash in FinalizeJob on the PR #8894 run (EACCES creating under the runner home directory) left the PR with no review and no explanation. - Probe write access to $HOME, $RUNNER_TEMP and the runner root at job start, repair single-directory ownership with the existing sudo pattern, and fail fast with a clear message when repair is impossible instead of burning the review budget to die at finalize. - Add a fallback-comment job on an ephemeral hosted runner that posts the retry guidance whenever review-pr fails. It derives the PR number from the event payload (dead job outputs do not survive a crash) and dedupes on a qwen-review-fallback comment marker plus this run's URL, so the in-job step, the ack comment, and re-runs never double-post. * fix(ci): harden the PR review fallback comment (#9255) Review-round fixes for the fallback-comment defenses: - Probe the actual runner root (three levels above the workspace, not two) and the _diag subdirectory FinalizeJob writes in; a writable parent does not prove an existing subdirectory writable. - Open the fallback gate on authorize/review-config failures too — the incident's trigger can kill those earlier self-hosted jobs first, and a failed dependency marks review-pr 'skipped', which the old gate never matched. Guarded against resolve dispatch runs, which skip review-pr by design. - Author-scope the dedup lookup (resolved dynamically like upsert-bot-comment.sh) so a planted marker cannot suppress the fallback, and fail closed with bounded retry when the lookup or the state check fails instead of fail-open toward duplicates or a green job that never posted. - Skip the stale fallback when the PR head moved, but only on pull_request_target events where the run head is comparable — comment/review runs report main's tip, and posting wins over silence when the comparison is unavailable. - Define the marker once in a workflow-level env and pin all of the above in the workflow test suite, executing the fallback step's real bash against a stubbed gh. * fix(ci): close the fallback-comment gate gaps from round-2 review (#9255) - Exclude comment-driven /resolve runs from the fallback gate: authorize runs on `@qwen-code /resolve` issue comments where github.event.inputs is empty, so the dispatch-only exclusion never fired there and a failed resolve run was misdiagnosed as a dead review recommending the wrong command. - Enumerate precheck-pr and delay-automatic-review failures in the gate: either failure marks review-pr 'skipped' (a transient API 5xx in delay's re-check step, or the fork-PR chain root dying before it posts anything), which the old gate never matched — silence, against its own "a skipped review is as unexplained as a dead one" norm. Both are 'skipped' where they do not apply, so the gate stays closed there. - Anchor the cross-job dedup on the run URL's closing paren: run ids grow digits over time, so the unanchored substring let a later run's fallback comment (id 123450) suppress an earlier run's (id 12345) re-run comment; every marker body renders the URL as [workflow logs](...runs/<id>), so the id is always followed by ')'. - Pin the previously surviving mutants in the workflow suite: the _diag probe guard polarity, the workflow_dispatch disjunction, the ephemeral-hosted-runner placement, and the fallback body's marker-link shape the anchor relies on; add executed coverage for each head-lookup partial failure and for a distinct run's fallback not suppressing this run's comment. * fix(ci): close round-3 review gaps in the fallback-comment defenses (#9255) --------- Co-authored-by: qwen-code-ci-bot <qwen-code-ci-bot@users.noreply.github.com> |
||
|
|
a9bff6c9b8
|
feat(autofix): defer verified out-of-footprint findings to a surviving follow-up queue (#9189)
* feat(autofix): route verified out-of-footprint findings to a surviving follow-up queue
Anti-drift closure for the review loop: a finding that is REAL but whose
fix lies outside the PR's footprint previously had only wrong outcomes —
implement it (scope drift), decline it (the finding is lost when the PR
merges and nobody re-reads its threads), or push it onto a maintainer.
- SKILL gains the fourth disposition, Defer to follow-up: verified +
out-of-footprint → record {id, path, reason} in
deferred-findings.json, reply on the thread that it is deferred, leave
the thread open. Decline stays for what is not worth doing anywhere;
defer is for what is worth doing elsewhere.
- The report step upserts these into one per-PR 'Deferred review
findings' issue (marker-keyed, append-only by rc id, agent text
token-neutralized and length-capped), for BOTH pushed and no-op
outcomes. Best-effort: an upsert failure never fails a round.
Deliberately no ready-for-agent label — feeding the bot's own
deferrals back into its issue queue is a human authorization.
- deferred-findings.json rides the artifact dump and the repair
cleanup; the neutralization ledger grows to ten sites.
- Tests: shape validation (non-empty array of numeric-id items), line
building (dedupe by rc id against the existing issue body, newline
flattening, truncation), and wiring pins for both call sites.
* fix(autofix): rebuild the deferred-findings upsert per review round 1
- Extracted to a trusted staged script callable from ALL outcome paths —
the failure/handoff path persists verified findings too (a failed
round's commit dying says nothing about the findings' validity).
- Append-only durability: the tracking issue's body is written once;
every later round POSTS a comment — no read-modify-write can race a
maintainer's edits, and a failed body/comments read SKIPS the round
(never mistaken for empty history). Success is logged only when the
write call succeeded; failures say NOT persisted.
- Structured lookup: jq filtering over the real bodies (no line-joined
awk under pipefail), pull requests excluded, lookup failure skips
rather than creating duplicates.
- Dedupe is line-anchored ('- rc:<id> ' at line start, body+comments
corpus) with intra-batch unique_by; ids the round resolved in code are
excluded (a finding cannot be implemented and outstanding at once).
- Shape gate covers path (string when present); path bytes are
charset-sanitized so a crafted path cannot forge queue bullets.
- Publication-trust posture recorded: the deferred lines are the same
agent-authored trust class as every other published output — marker
neutralization, mention-free sanitized charset, length caps, and a
20-item batch cap bound the surface.
- Tests: the real script runs against a recording gh stub — create,
append+dedupe (body and comments), PR-carrying-marker exclusion,
anchored dedupe vs free-text mentions, read-fail skip,
resolved-exclusion, shape-gate loudness, write-fail honesty, and
forged-path sanitization; the neutralization ledger returns to nine
workflow sites with the script-side tenth pinned in place.
* fix(autofix): harden deferred-findings upsert per review round 2
- Pass the known-id corpus to jq via --rawfile: a large corpus in one
--arg argv element hits Linux MAX_ARG_STRLEN and the swallowed exec
failure would silently drop the round's deferrals.
- Digest-gate the staged upsert script: record upsert_sha256 at stage
time (expression context) and verify before each of the three
invocations; RUNNER_TEMP is agent-writable in between. A mismatch
skips persistence, never the round.
- Add the gh hygiene preamble (GH_HOST pin, GH_TOKEN unset, fresh
GH_CONFIG_DIR) to the review-address failure/handoff report step —
the one PAT-bearing gh step that lacked it.
- Query the tracking-issue lookup with state=all so a maintainer-closed
issue is appended to instead of forking a duplicate.
- Enforce integer positive finding ids in the shape gate (a float id's
dot is a regex wildcard in the anchored dedupe and never
index()-matches resolved ids).
- Clip the 20-item batch loudly and qualify success messages with
kept/total counts instead of claiming full persistence.
- Tests: digest + hygiene wiring pins; stub knobs for list/comments
fetch failures; append-write failure, multiline-reason flattening,
bad-id, loud-cap, and state=all cases.
* fix(autofix): close bash/transport channel gaps and failure-path gate holes (review round 3)
- Sweep BASH_ENV/ENV and imported BASH_FUNC_*%% functions plus proxy
(HTTPS_PROXY/HTTP_PROXY/ALL_PROXY + lowercase) and SSL_CERT_FILE/DIR
at all four gh-hygiene sites: both families are GITHUB_ENV-plantable
and bypass the TRUSTED_PATH pin (child-bash startup) or reroute/
decrypt PAT-bearing HTTPS. Also de-shadow gate-critical names and
hash -r, since a planted BASH_ENV runs before the step body.
- Pin PATH to the staged trusted value (guarded for pre-stage crashes)
and drop the loader trio in the failure/handoff report step — its
digest gate previously ran under ambient PATH/LD_PRELOAD.
- Failure-path upsert: skip with a plain notice when stage never ran
(empty digest is not a tamper alarm), and verify the PAT's bot
identity before writing (POST_HANDOFF's check is skipped on the
fixed/noop-outcome path); correct the guard comment that claimed
parity with the handoff guards.
- Fold the twice-pasted digest-gate + upsert block in 'Push and report'
into a step-local run_deferred_upsert(), matching the
resolve_and_reply_threads convention.
- Dedupe corpus reads bot-authored comments only, so a third party
commenting on the public tracking issue cannot suppress a finding.
- Tests: hygiene sweep pins ordered before the first gh call; placement
assertions (function defined once, called after both resolve arms;
failure invocation inside the DRY_RUN/STALE/token guard slice);
digest/identity/notice pins; behavioral cases for foreign-author
suppression, intra-batch duplicate ids, markerless-issue create path,
creator/marker anchors, and marker neutralization through the append
path.
* fix(autofix): isolate deferred-upsert in a clean env -i child, drop the unsound in-shell sweep (review round 4)
Round 4 (R4-1, five Criticals of one class) showed the in-shell
BASH_FUNC/proxy denylist sweep the prior round added is unsound: it
bootstraps trust from the very shell namespace it sanitizes, and a
planted BASH_FUNC_env%%/unset%%/command%%, an expand_aliases alias, a
readonly -f shadow or a DEBUG trap each defeat it — ending in the
staged upsert script executing with CI_DEV_BOT_PAT in env.
- Replace it with sound isolation: both upsert sites (run_deferred_upsert
in 'Push and report', and the failure/handoff path) run the digest
gate, the PAT identity check and the staged script in a fresh
'/usr/bin/env -i … bash --norc -c' child. /usr/bin/env is invoked by
absolute path — bash never does function/alias lookup on a
slash-bearing word, so a planted BASH_FUNC_env%% cannot intercept it —
and env -i drops every BASH_FUNC_*, BASH_ENV, SHELLOPTS, alias and
trap before any gated work. GH_CONFIG_DIR is minted inside the clean
child (its mktemp cannot be shadowed there), closing the mktemp-shadow
hole in the failure step's preamble.
- Remove the sweep and the in-step gh/PATH preamble the prior round
added to all four PAT gh steps; the three pre-existing steps revert to
their prior posture. Hardening the pre-existing PAT gh calls
(handoff/report comment, push, publish) against BASH_FUNC/transport
plants is noted as separate, out of this feature's scope.
- Script: accept a contract-valid empty array as a clean no-op instead
of a false 'malformed' alarm; 'set +C' so a planted read-only
SHELLOPTS=noclobber cannot silently empty the dedupe corpus (belt to
the env -i child that already drops SHELLOPTS).
- Tests: replace the sweep pins with clean-child pins (absolute-path
env -i at both sites, GH_CONFIG_DIR/PATH inside the child, failure
launch inside the guard slice); add no-findings exit-0, empty-array
no-op and set +C cases. Behavioral probe: a fully tainted parent
(BASH_FUNC/alias/BASH_ENV plants) cannot reach into the env -i child.
* fix(autofix): strip LD_* before the env -i upsert child; tighten id gate, mktemp/cap guards (review round 5)
- R5-1 (Critical): LD_PRELOAD/LD_AUDIT/LD_LIBRARY_PATH is the one channel
env -i cannot block — ld.so maps a planted library into /usr/bin/env
itself at execve, before -i wipes anything. Neutralize it with a
command-prefix assignment (LD_PRELOAD= LD_AUDIT= LD_LIBRARY_PATH=)
before /usr/bin/env at both upsert launch sites: a pure shell
parameter assignment no BASH_FUNC can shadow, applied to env's own
environment. Probe: a parent LD_PRELOAD=/evil.so no longer reaches the
env binary or the child.
- R5-3: shape gate rejects integer-valued floats jq renders in
scientific notation past 2^53 (1e21 -> "1E+21") — the '+' is a
regex-active byte in the anchored dedupe. Add a <2^53 bound and a
tostring plain-digits belt.
- R5-4: guard mktemp failure (a known /tmp-exhaustion CI state) so it
warns and skips instead of a silent exit 0 that violates the header
contract.
- R5-2: the cap warning no longer promises 're-defer in a later round'
(impossible — the eval-watermark filters evaluated feedback out
permanently); it names the dropped bullets for a maintainer.
- Tests: LD prefix, GH_HOST-in-child and gate/exec ordering pins; tie
the two near-verbatim clean-child bodies together (R5-6); source pins
for both --paginate sites (R5-5); if-!-echo gate-condition pin (R5-9);
failure step added to the sweep-removal regression loop (R5-11);
behavioral cases for sci-notation id, mktemp failure, prefix-colliding
dedupe boundary (R5-10) and the reworded cap.
* fix(autofix): defuse mentions in deferred bullets, verify child liveness (review round 6)
- R6-9 (Critical): the reason is agent-influenced prose published under
the bot identity, so a raw @ fired real mentions from the tracking
issue. Defuse before rendering: @ gets a trailing ZWSP, and the entity
spellings GitHub decodes BEFORE its mention filter (@ @
@ @) get their & escaped. Both measured inert against the
real renderer; \@ and leaving & alone are not. Paths were already
charset-reduced. Byte-exact probe: 2/2 @ defused, all four entity
spellings escaped, no raw spelling survives.
- R6-8: LD_* cannot be enumerated — LD_TRACE_LOADED_OBJECTS is
presence-tested, so even the empty prefix assignment leaves trace mode
on and /usr/bin/env prints its libs and exits 0 without ever running
the child (probed). Verify the RESULT instead: the child prints a
liveness sentinel first and its absence is reported. The inspection
uses bash builtins only — an external grep would itself
print-and-exit-0 under trace mode, neutering the check (measured: the
first grep-based attempt failed exactly this way).
- R6-4: guard the child's own GH_CONFIG_DIR mktemp; an empty value falls
back to the shared ~/.config/gh.
- R6-6: a line-builder (jq/sed) failure warns instead of exiting
silently as 'nothing new' — the last path skipping the header
contract.
- R6-3: write-failure warnings say the findings are LOST (watermark-
gated, never retried) and name the bullets, matching the round-5 cap
wording fix.
- Tests: allow-list entry pins (R6-7), sentinel/builtin-inspection and
child-mktemp pins, identity-check ordering (R6-5), plus behavioral
cases for mention defusing, reason-type and id-0 gate clauses (R6-2)
and line-builder failure.
* fix(autofix): defer findings from all three feedback sources; carry them across repair (review round 7)
- R7-1 (Critical): only inline comments carried an id in feedback.md, so
a verified out-of-footprint finding raised in a review body or an
issue-level PR comment could not be deferred and was lost at merge.
Feedback now renders [rv:<id>] and [ic:<id>] alongside the existing
[rc:<id>], the record takes an optional "source", and bullets anchor
under a per-source prefix so id spaces cannot collide. The resolved-id
exclusion stays inline-only (that is what resolved-comments.txt holds).
SKILL documents all three sources and that only inline findings have a
thread to reply on.
- R7-2 (Critical): every abort path said "skipping ... this round",
implying a retry that cannot happen (the eval watermark filters this
round's feedback out of every later round and the next reset wipes the
file). All six now report the findings as LOST and dump the raw
deferrals for manual recovery, with :: neutralized — the dump is
agent-influenced and a raw :: at line start is a workflow command.
- R7-3 (Critical): 'Repair deterministic rejection' deleted
deferred-findings.json before any upsert site ran, so run 1's
deferrals died in a repaired round. It now carries them into a sidecar
the upsert unions in (merging if an earlier repair left one), and the
sidecar rides the artifact dump. A/B probe: base arm loses run 1's
deferral, fix arm persists both.
- R7-4: a present-but-non-string path (false) passed the gate because //
treats false as absent; the gate now tests .path|type directly.
- R7-5: LD_PROFILE/LD_PROFILE_OUTPUT/LD_DEBUG/LD_DEBUG_OUTPUT are
non-blocking loader file-write channels the liveness sentinel cannot
catch, so they join the command-prefix neutralization (probed inert
when empty).
- Tests: per-source anchoring and dedupe, unknown-source rejection,
path:false, carry-only and carry-merge cases, LOST dump with ::
neutralization, plus wiring pins for the carry, the feedback ids and
the extended LD prefix.
* perf(autofix): bound the deferred-issue lookup and name gh failure causes
Clears the two backlog items the review has re-raised every round since
round 2 (R2-6, R2-10); both live in the file this PR adds.
- R2-6: the tracking-issue lookup ran a full --paginate over every issue
the bot has ever opened, on every round that defers anything, keeping
only the first match. It now walks newest-first pages and stops at the
first marker match: one request in the common case, a short page ends
the scan (corpus exhausted -> create), and a 10-page cap bounds the
worst case. Reaching the cap without a match SKIPS rather than opening
a second tracking issue for the same PR.
- R2-10: every gh call discarded stderr, so a rate limit, an expired PAT,
a transport error and a 404 rendered identically in the feature's only
signal. All five calls now capture stderr to one sink and the warnings
name the cause, :: neutralized like every other echoed API/agent
content.
Measured on the shipped script with a recording gh stub: first-page hit
1 request, empty corpus 1 request + create, page-2 hit 2 requests, cap
10 requests + skip, and the 403/401 bodies reaching the warning text.
* test(autofix): close the pin gaps the round-8 mutation sweep found
Round 8 raised 15 findings, none Critical: two behavioural, the rest
test pins the reviewer proved vacuous by mutation.
- R8-3: the carry-merge failure branch discarded THIS run's deferrals
with no raw dump — the one loss path in the feature without recovery
output. It now prints the set (:: neutralized) before deleting it.
- R8-1/2/5/6/7/8/9/10/11/12/13/14/15: pins that survived their own
mutations. Notably: the negative sweep pins now assert the PROPERTY
(no BASH_FUNC / unset -f / hash -r in non-comment lines) instead of
round 3's exact spelling; allow-list entries must sit inside the
env -i argument list, not merely somewhere in the step; the identity
check is pinned whole so a fail-OPEN mutation cannot pass; the repair
cleanup's deletion pin is spelling-independent and allows exactly the
one delete that follows a merge; the staging pins are scoped to the
stage step with cp ordered before the digest record; and the script's
<!-- escape site gets the count+canonical treatment its workflow
siblings already had.
Each new pin was mutation-verified: 8/8 injected regressions turn the
suite red (differently-spelled sweep, relocated allow-list entry,
deleted GH_CONFIG_DIR export, fail-open identity check, re-added
cleanup deletion, deleted re-print loops, ascending lookup order,
no-op sed spelling).
* fix(autofix): close the upsert TOCTOU and the per-id deferral collapse (review round 9)
- R9-1 (Critical): the digest gate was check-then-use — sha256sum read
the staged path and bash re-opened it, two opens of a path this PR
itself calls agent-writable. The child now reads the script ONCE and
runs those exact bytes (bash -c "$UPSERT_SRC"), so the bytes hashed
are the bytes executed. A/B against an inotify-driven same-user
rename(2) watcher: old shape 20/20 payload executions with the gate
never firing, new shape 0/20 (legit 20/20, control 20/20).
- R9-2 (Critical): unique_by([source, id]) collapsed DISTINCT findings
sharing one review-body or issue-comment id — the two sources this PR
adds — and reported success while losing them. Dedupe identity and the
corpus check are now per rendered line for those sources (inline
comments keep id identity and the cross-round anchor). Probe: two
findings under one review id now both persist ("3 of 3 new"),
byte-identical records still collapse, inline behaviour unchanged.
- R8-4 (re-raised): fixed structurally instead of by the suggested
prefix entry, which is a no-op — LD_SHOW_AUXV is presence-tested, so
an empty assignment still dumps 22 auxv lines (measured; env -u does
not help either). Loader side channels write to the LAUNCH process's
stdout, so that stdout is discarded and the child logs to a private
file; path and read-back are fork-free ($$ expansion, $(<file)) so a
polluted parent cannot leak noise into the value. Measured: with
LD_SHOW_AUXV planted the log holds 0 auxv lines and the child still
runs; with LD_TRACE planted the sentinel is absent and the warning
fires.
- R9-3/4/5: the manual-recovery dumps now say when they truncate, and
name the full byte count.
- R9-11: the artifact dump neutralizes :: in the agent-written files it
prints, like every other echo of them.
- Tests: pins for the single-read exec, the private log, the fork-free
parent handling, the identity check's ENFORCEMENT (R9-9), an
allow-list that must hold ONLY the sanctioned entries (R9-10), the
sentinel comparison inside the re-print loop (R9-13), and R9-2's
multi-finding cases. Also fixes an argList slice that anchored on a
comment mention and silently widened to the whole step.
* fix(autofix): keep a poisoned carry from sinking the round; clear the pin backlog
Clears the eleven items carried from round 9.
- R9-18: a carried sidecar that PARSES but fails the shape gate used to
abort this round's valid deferrals too — asymmetric with the
unparseable-carry branch, which persists this round only. The gate is
now a function applied to the merged set, with a retry on this round's
own file; the carry is dumped and named LOST. Measured on the real
script: valid own + gate-invalid carry -> own persisted, carry dumped;
valid own + unparseable carry -> own persisted; invalid own -> loud
total abort, nothing written.
- R9-20 / R9-7: the union argument order IS the freshness guarantee
(jq unique_by keeps first-of-group in original order), pinned at both
sites; measured: a duplicate id keeps this round's text, not the
carried one.
- R9-3/4/5 follow-up: the three truncation dumps became one dump_file
helper instead of a fourth copy.
- Pins: --paginate anchored to the comments call (R9-8), the stale
two-sites comment corrected (R9-6), the no-in-shell-sweep property
widened past function-unset spellings to alias/trap/proxy forms
(R9-12), the repair cleanup's deletion pin extended to rm -rf and to
any second multi-line list (R9-16), runUpsert's spawnSync bounded like
its sibling harness (R9-17), the explicit review_comment spelling
covered (R9-19), and the 20-item cap's survivor set pinned from a
MEASURED run whose sort order and input order disagree (R9-14) — the
four records written first are the ones dropped.
Mutation-verified 6/6: relocating --paginate, an alias-form sweep, an
rm -rf deletion, either union order swapped, and dropping the
poisoned-carry fallback all turn the suite red.
* test(autofix): widen the denylist-sweep guards past their word-boundary hole
`\btrap -\b` cannot match `trap - ERR EXIT`: the boundary sits between
`-` and a space, both non-word characters. The same hole was in two
sibling guards — `\bunset -f\b` misses `unset -fv name`, and
`\bhash -r\b` misses any suffixed spelling. Drop the trailing
boundary on all three.
Mutation-verified: injecting `trap - ERR EXIT INT TERM`, `trap -- EXIT`
or `unset -fv sha256sum` into a PAT-bearing step now turns the suite
red; each passed before.
* fix(autofix): two -e-fatal paths, an escape-order dedupe hole, and a rewording duplicate (review round 10)
Six Criticals; four were defects this PR introduced.
- R10-19 + R10-22 (Critical): the PAT steps run under 'bash -eo
pipefail' (defaults.run.shell: bash). Measured: 'rm -f' on a planted
DIRECTORY at the predictable log path exits 1 and kills the step, ': >'
onto one likewise, and $(<missing) is fatal in a way NEITHER '|| true'
NOR 'if !' rescues. Creation is now 'rm -rf' + '(set -C; : >)' with a
warn-and-skip, and the read-back tests -f/-r first while staying
fork-free. Probed all four planted shapes (fresh/file/symlink/dir):
the step survives each.
- R10-5 (Critical): the <!-- neutralization ran in a sed AFTER the jq
corpus comparison, so an rv/ic line carrying <!-- compared its RAW
rendering against the ESCAPED stored form — never matched, republished
every round. Escaping moved inside jq, before the compare.
- R10-17 (Critical): rv/ic identity was the exact rendered line, so any
reworded re-emission (routine: the repair flow re-runs the agent)
published a permanent duplicate. Identity is now a normalized digest —
case-folded, punctuation-collapsed, trimmed, capped — which absorbs
phrasing churn while keeping distinct findings apart. The tension with
R9-2 is real and resolved deliberately toward a visible duplicate over
a silent loss.
- R10-1 (Critical): the sweep tripwire is a spelling denylist over an
unbounded space. Reframed as what it is — a drift alarm, not the
boundary (the boundary is the env -i child, pinned separately) — and
aimed at the ENUMERATION PRIMITIVES a sweep needs (compgen -e,
declare -x, env pipes, export -n) instead of more name vocabulary.
- R10-6 (Critical): the jq stub hardcoded /usr/bin/jq, which does not
exist on macOS; it now resolves through the original PATH.
- R10-13: the child's log comes from agent-writable RUNNER_TEMP, so ::
is neutralized on re-emission via parameter expansion (no fork).
- R9-5 leftover: the third truncation dump now names the clipped size.
Mutation-verified 5/5 and behaviour-probed 5/5 (escape-before-compare,
reworded re-emission, distinct sibling, R9-2 no-regression, planted-path
shapes).
* fix(autofix): make the deferral identity lossless; keep the unmerged set on disk (review round 11)
Two Criticals, both defects this PR introduced, plus the eleven items
carried from round 10.
- Identity key (Critical): the normalized key stripped every non-[a-z0-9]
byte and capped at 160 chars, so CJK siblings collapsed to one key (this
repo is bilingual) and a long path pushed the reason out of the identity
entirely — silent loss, the exact outcome the feature exists to prevent
and the opposite of what its own comment claimed. The key now normalizes
case and PUNCTUATION only, keeping every letter of every script and no
cap, at both the build and corpus sites. Rewording tolerance is
unchanged. Probed: 2 CJK siblings -> 2 of 2; 2 siblings on a 200-char
path -> 2 of 2; reworded duplicate -> 1 of 1.
- Repair merge failure (Critical): the branch deleted
deferred-findings.json before 'Show run artifacts' and the artifact
upload ran, so its own pointer at the artifact dump was false past the
4000-byte clip. It now renames the set to deferred-findings.unmerged.json
(kept in WORKDIR, added to the dump list) on the failure path and deletes
only on the merge-success path. Probed: 6245 bytes preserved where the
dump clipped at 4000.
- R10-12: neither side is known to be the corrupt one (jq -s fails if
either input is unparseable), so both merge-failure warnings say that
instead of blaming this round.
- R10-18: a second identity anchor — the derived title — so an edited body
that loses the marker no longer orphans the issue into a duplicate; the
marker still wins when both are present, and a same-titled PR is still
never adopted.
- R10-3: the carry branch is unreachable in today's topology (WORKDIR is
wiped at run start, one repair step); kept as defensive with that stated.
- R10-10: the builtins-only discipline is scoped to the child-output
INSPECTION, which is what it always meant.
- Pins: delimited-token allow-list incl. UPSERT_LOG's value (R10-7), every
respelling of executing the staged path (R10-8), the tripwire extended to
the issue-autofix failure steps (R10-11), launch-line anchoring in both
steps (R10-15), flagless rm counted (R10-16), and behavioural cases for
the 200/500 caps (R10-4), CJK and long-path siblings, and the title
anchor.
Mutation-verified 7/7.
* refactor(autofix): remove the agent-writable paths the upsert depended on (review round 12)
Rounds 9-12 each closed one hole in a design that read the staged script
from an agent-writable path and buffered the child's output through
another. Round 12 found four more of the same class (TOCTOU on the log
reopen, a plantable FIFO and an unbounded read on each path). Rather than
patch a fifth time, remove both paths.
- The script travels as CONTENT: the stage step captures it from the
trusted checkout into a step output (random heredoc delimiter), and the
clean child runs `bash -c "$UPSERT_SRC"`. With no agent-writable copy
there is nothing to verify — the digest gate, its check-then-use
window, the staged cp and the FIFO/huge-file read all disappear.
- The child's messages travel on fd 3, which the parent captures, while
fd 1/2 are discarded. Every loader side channel writes there, so the
noise still cannot reach the parsed output — and there is no log file
to plant, race, bound, or clean up. Probed with LD_SHOW_AUXV and
LD_TRACE planted: clean output, sentinel behaviour unchanged.
- RC-1: resolved-comments.txt went to jq as one argv element, the exact
MAX_ARG_STRLEN failure the neighbouring comment describes and that
`known` already avoided. Both corpora use --rawfile now. Measured on a
348 KB corpus: the old form dies with "Argument list too long", the new
one publishes normally.
Net -113 lines, and the pins follow: no-path invariants replace the
digest/log battery. Mutation-verified 6/6.
* fix(autofix): reject multi-document deferral files; survive a base without the script (review round 13)
- Multi-document JSON (Critical): `jq -e` without -s evaluates each
document in turn and its exit status reflects only the LAST, so
`[valid]\n[]` exited 0 silently (findings lost, no warning) and
`[bad-id]\n[valid]` passed the shape gate outright. A single_doc gate
now runs first, with the asymmetry the earlier rounds settled on: a bad
OWN file is a total abort, a bad CARRY costs only the carry.
- R13-7: the stage step reads the script from the TRUSTED BASE, where it
does not exist until this PR merges — under -e that killed every
pre-merge pull_request-triggered round (true of the old cp too, so this
has been latent since the script was added). It now tolerates the
absence and lets the consumers' own empty-content guard skip the round.
- R13-2: the carry union requires both inputs to BE arrays; `add` on two
non-arrays yields whatever they add to.
- RA1R4-B: the resolved-corpus test is -f/-r, so a directory or FIFO at
that path is treated as unusable rather than present.
- R13-1: nine rationale comments still described the staged copy and the
digest gate that round 12 removed.
Probed: both multi-document shapes are rejected loudly with zero writes;
a bad carry still leaves this round publishing 1 of 1.
* docs(autofix): retire the last stale rationale comments (R13-1)
Six of the nine locations were in the test file: comments still describing
the digest gate, the staged copy and the read-once invariant that round 12
removed. Same honesty issue as their three workflow siblings.
* fix(autofix): strip BSD wc padding; let wrapper warnings stay annotations (review round 14)
- BSD wc (Critical): `wc -l` pads its count with leading spaces on
macOS, and TOTAL_NEW is interpolated into the cap warning and the
success line — the sibling `wc -c` already stripped it, this one did
not. Tested with a padding `wc` stub, since GNU wc never pads and the
regression is invisible on Linux CI otherwise.
- The re-emit loop demoted the feature's own failure signal: every `::`
became `;;`, including the wrapper's trusted messages. Wrapper-authored
lines now carry a marker and are emitted VERBATIM (so they render as
annotations again); the script's output, which interpolates agent
content, stays neutralized.
- \b on `declare -x`/`export -n` had the same word-boundary hole already
fixed for `trap -`/`unset -f`.
- Pins: the heredoc CLOSING delimiter, the merge-failure quarantine
rename, and an allow-list comparison that is a sorted multiset rather
than a Set — a symmetric same-name addition is exactly what that check
exists to catch and a Set hid it.
Mutation-verified 5/5.
* fix(autofix): un-truncate the sibling identity; align the carry precedence (review round 15)
Zero Criticals this round; five behavioural findings among the pins.
- The rv/ic intra-batch identity was derived from the RENDERED line, i.e.
after the 500-char reason cap, so two siblings differing only past the
cap collided and one vanished silently — the same silent-loss class as
the CJK and long-path entrances. It now comes from the uncapped
path+reason; the corpus check still compares rendered forms (that is
all the issue stores), so cross-round the cap can cost a duplicate,
never a loss.
- The repair carry union put the OLDER set first, inverting the
newer-wins precedence the script documents for its own union.
- The resolved-id parser dropped any line with stray surrounding
whitespace, so a padded `rc:<id>` no longer suppressed its finding.
- The clean child's catch-all warning lacked the trusted marker added
last round, so the feature's most common failure message was still
demoted out of annotation form.
- The truncation notice pointed at the artifact dump even when the
dumped file is a merge temp outside WORKDIR, which is never uploaded.
Pins: the stage step's `id: 'stage'` (the link whose break empties every
UPSERT_SRC), the empty-content skip branch, the capture's `|| true`, and
the trusted marker on every warning inside the child.
Mutation-verified 6/6.
|
||
|
|
80e825c54c
|
fix(review): fix silent reverse-audit retirement failures and keep non-converged evidence (#9213)
* fix(review): name reverse-audit certification failures, keep non-converged evidence (#9206) A round-5 reverse audit (PR #9118, 12 chunks) never retired a chunk: four territories returned substantive dry receipts in BOTH rounds 1 and 2, yet rounds 3-5 rebuilt all 12 auditors, no retirement note appeared, and nothing anywhere said which certification condition refused the receipts. Step 9 cleanup then deleted the prompt-record directory of the non-converged run, so the failure could never be diagnosed. Two root causes, reproduced end to end (issue-9206-repro.test.ts): - The dry-receipt separator class admitted dashes and colons only, but honest receipts arrive separated by sentence punctuation too — "No new issues were found. Re-walked …" and "未发现新问题,重新走查了…" both reproduce the never-retire loop byte for byte. The clause after the separator is the part that proves the walk; the separator only has to show it exists. Widen the class to period, comma and semicolon in either width. The substance floor, the tool-call bars and the territory bar are unchanged — the bare stock sentence still reads unknown, and a yield still outranks any receipt. - Every certification refusal landed in the same silent unknown, and the schedule's catch swallowed every exception without a word. classifyReturn now reports the first bar that fell; the schedule carries one diagnostic line per twice-audited chunk that neither retired nor yielded, and the builder prints them on stderr (stdout stays the deliverable). The catch names itself: a round whose transcripts cannot be read audits every chunk AND says so. And the evidence half: cleanup swept the record directory of a non-converged run unconditionally — the round-cap marker that proves the non-convergence sat inside the very directory deleted. A record directory holding a same-run stop marker is now kept, with a note naming it; a converged run clears its marker, so its history sweeps as before. * fix(review): refuse hedged receipts, keep cross-run audit evidence (#9213) The widened receipt separator admitted clauses that contradict the no-issues phrase ("…found, but I could not open the files") and let a quoted phrase open a clause out of its own negation, retiring chunks on their auditor's admission that nothing was checked. Judge polarity on every separator path and admit sentence-punctuation separators only when the phrase leads the return. Cleanup's retention keyed on the run-epoch-fenced marker reader, so a previous run's preserved evidence was swept by the next run — and clearBudgetStop unlinked any marker, including a previous run's that retention had just kept. Retention now reads the marker unfenced and also keeps record directories carrying files older than the plan's own capture (a killed run leaves no marker); convergence clears only its own run's marker. The per-chunk build path now prints its chunk's certification-failure diagnostics like the round builder does. * fix(review): diagnose every chunk build, invert the polarity burden (#9213) The per-chunk gate stamped the round on its first chunk build and gated the certification diagnostics behind that stamp, so chunks 2..N of a one-auditor-at-a-time round re-audited in the exact silence the note exists to end. Print the chunk's diagnostic on every build of the round; only the convergence and budget rulings stay gated on admission. The polarity guard enumerated contrast words over unbounded prose, and prose has no last hedge — though/Yet/unfortunately/只是 all retired chunks on clauses admitting nothing was checked, while a hedge in the phrase's filler never reached the clause-only test at all. Invert the burden: a clause carrying a negation or incapacity marker contradicts the phrase whatever its length, and a contrast word without one contradicts nothing — an innocuous "but I re-verified" retires again, and the guard's remaining gaps land on the audit side. The anchored receipt matcher now leads (the unanchored one truncated clauses at nested phrase occurrences), the judged text is re-trimmed after the budget-gap strip, and cleanup keeps a record directory whose plan a previous cleanup already swept and skips unstatable entries one by one instead of letting one veto the previous-run evidence. * fix(review): line-scope the polarity guard, case-blind parrot, safe notes (#9213) The polarity guard read only the match's own prefix and the clause, so a hedge BEFORE the phrase on the same line was invisible and retired the chunk (`I could not check everything, but no new issues — re-walked…`), and its marker list missed the incapacity/omission families the leak probes executed — unable/failed/skipped/unchecked/skimmed, zh bare-不 and 跳过. Scope the guard to the LINE the receipt matched on — the match plus any same-line text before the phrase, quoted spans exempted — and name the executed marker families. Prose on any other line neither certifies nor contradicts: scanning the whole return would refuse an honest receipt beside an innocent "not re-reporting it" paragraph. The substance floor now measures the phrase-STRIPPED clause (greedy with the phrase's filler), so an echoed phrase cannot lend the floor its length, and the comment's direction claim is corrected — a marker the list misses inside the line still fails toward RETIREMENT, stated rather than papered over. The parrot refusal compared case-sensitively, but the example clause starts lowercase only because it continues the model receipt mid-sentence; the widened sentence-punctuation separators let a parroting auditor open it as a NEW sentence and capitalize it. Compare in any casing — no honest clause contains the model clause verbatim in any casing. The three new informational stderr writes — both schedule catch NOTEs and the uncertified-chunks note — used the throwing writeStderrLine on the CONTINUING build path. A headless retry with stderr redirected or closed (the very #9206 shape) made the write throw out of a catch with no outer guard, abandoning the round that must audit every chunk. Use the Safe writer, matching writeFindingsFile. * fix(review): close the polarity guard's unbounded class with one receipt form (#9213) The polarity guard enumerated hedges over unbounded auditor prose, and every hedge the list missed failed toward RETIREMENT — the direction the module header declares impossible. Round 5 executes the structural fix the round-4 Critical named: one machine-parseable receipt form, and everything else read as unknown. The form: the phrase LEADS the return (anchor; the lead CONTRAST list is deleted), the receipt line stands ALONE (Budget gap: and Layer walked: lines stripped with audit-layers' own matcher; any other prose before or after reads unknown, named for the side it fell at), and the clause NAMES THE WALK (a verb from the form's vocabulary, or a named object — misses fail toward audit, the opposite direction of a marker miss). Within the form the marker test is repaired, not extended: the strip uses the phrase core without the greedy tail that swallowed its own listed markers, and the quoted-span exemption that blanked a self-admission is deleted. Zero words added to the vocabulary. Also closes R4-1: the clause capture ran to the END of the return, so prose after the receipt line contradicted the phrase while identical prose before it passed. The capture stops at the line; both sides now read unknown, symmetrically named. The reverse-audit brief mandates the form so compliant dry returns stay one line and keep retiring. * fix(review): line-bind the dry-receipt matcher and cut fused layer labels (#9213) --------- 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> |
||
|
|
c7d496c558
|
fix(review): normalize last-gate inputs and anchor mid-line fragments (#9222)
* fix(review): normalize last-gate inputs and anchor mid-line fragments The /review pipeline's final gates rejected the shapes its own earlier stages produce (#9209), failing hours-long runs at the finish line: - findings normalizes the bracketed Source: tags the finding format mandates ([probe] -> probe) instead of hard-erroring - findings --to-anchors writes the Step 7 resolver input straight from the canonical artifact (one {id, path, anchor, line?} per anchored location, aggregates expanded to <id>-N), replacing the hand-written projection from locations[]; expanded-id collisions are refused at projection time instead of failing the whole resolve batch - compose-review/submit accept the array form of suggestionsDiscarded, counted by length - the anchor resolver gains a substring-of-hunk-line tier so a verbatim fragment of a KB-long Markdown line resolves to the containing line (>=12 chars, single line only, ambiguity refused or claim-decided); SKILL.md documents the tier and the finder briefs gain the off-diff file anchor strategy * fix(review): refuse expanded-id collisions with every finding id Review of the projection and substring tier found the guards narrower than their promises: - the expanded-id collision guard only compared minted request ids, so a minted <id>-N equal to another finding's own id passed silently whenever that finding emitted no bare-id request (aggregate, low-confidence, anchorless, or Nice-to-have); seen is now seeded with every finding's id, self-match exempted - the projection runs before the findings artifact is written, so a refused rerun leaves the previous consistent findings/anchors pair on disk instead of v2 findings beside v1 anchors - anchorless locations of postable findings are named on stderr — nothing downstream cross-checks the artifact against the resolver input - a below-floor fragment that IS contained in a hunk line reports a dedicated "too short — quote a longer stretch" reason instead of the false generic absence reason; boundary probes pin the 12-char floor - the substring fallback retries the marker-stripped reading for true mid-line containment, forgiving a copied + marker without letting it double as an indentation guess - SKILL.md's display rule attributes the aggregate expansion to Step 6's findings --to-anchors, not Step 7 * fix(review): keep anchor refusals honest and write anchors before findings * fix(review): state only what the anchor resolver actually guarantees * fix(review): refuse the marker retry beside a live equal line Review round 4 found the substring tier's marker retry could still misplace an anchor, and the count contract's comments misdescribed the prose: - the retry dropped a line equal to the fragment modulo whitespace as a lineGuess, then resolved the fragment to an unrelated containment line at matchCount:1, ambiguous:false — a confidently posted misplacement where the old contract was a loud unmatched; the indentation refusal now outranks any remaining containment candidate while such a line exists, and a regression test pins it (fails pre-fix); the copied-+ marker case with no equal line stays green - SKILL.md scopes the unmatched disposition to standalone findings, adds the aggregate partial-resolution case (discarded and counted once per finding, not per entry), and names both claim-immune multiplicity reasons — the loose tier's "indentation was normalised" alongside the substring tier's "whitespace is normalised" - the three suggestionsDiscarded comments now say the Step 7 prose prescribes a count and the list form older runs wrote is tolerated, matching the shipped count-first prose * fix(review): split the aggregate all-unmatched disposition by severity --------- Co-authored-by: qwen-code-dev-bot <qwen-code-dev-bot@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
3186d4ea67
|
fix(review): exempt carried-id re-posts from the presubmit overlap drop (#9212)
* fix(review): exempt carried-id re-posts from the presubmit overlap drop The presubmit overlap gate was purely location-based, so a Step 6 ledger re-post — which lands on the original thread's line by construction — collided with the very comment it re-posts and was dropped, losing the carried id from the round's ledger marker. A same-line finding with a DISTINCT claim was also dropped invisibly. presubmit now accepts an optional finding id in --new-findings, additionally buckets id-matched overlap comments as repost (with matchedIds), and the skill's drop rule exempts findings whose id matches and names the overlapping comment (id + excerpt) on every drop. * fix(review): gate the carried-id exemption on authorship, harden id handling Cross-account ledger-id collision: ledger ids are scoped per account (two reviewers of the same PR each have their own R2-1), but the repost branch never consulted the comment's author — a different account's comment carrying a colliding id at the same line bucketed `repost` and exempted the finding, posting the duplicate the overlap gate exists to prevent. classifyExistingComments now takes the current user and runs the id match only on the reviewing account's own comments; location-based overlap stays cross-account. Regression test covers the different-account collision. Also: - the R<round>-<n> grammar now lives once in lib/ledger.ts (LEDGER_ID_TOKEN); presubmit's extractor and compose-review's prefix parser both compose from it, so the two ends cannot drift. - parseFindingsFile rejects present-but-misshapen ids (r3-2 / trailing space / empty) with the whole file — an accepted typo'd id can never match the extractor and would silently disable the exemption; three new table rows pin it. - the --new-findings yargs describe documents the id field. - extraction is pinned to the FULL raw body by a test whose carried id sits past the 80-char summary slice (the summary.body mutation no longer ships green). - the duplicated presubmitWithComments test helper is hoisted to the shared describe scope. - SKILL.md documents the known limitation that id-less originals (bodies without an id token) cannot be matched as re-post targets. * fix(review): treat id:null as a missing id; align schema directives Round-2 review of this change found three follow-ups: - parseFindingsFile rejected the WHOLE findings file on "id": null, but JSON has no undefined — a producer emitting the key uniformly uses null for id-less findings. One null entry flipped findingsFileInvalid, silently disabling overlap dedup and capping the verdict: disproportionate blast radius for a missing optional field. null now parses as "no id" (misshapen strings still reject); a table row pins it. - The schema's `overlap` line still said "BLOCK on submit if non-empty" directly above the repost exemption, contradicting it; rewritten to name the exemption. The byBucket line now notes that repost entries are a subset of overlap and counted in both. - DESIGN.md still said existingComments has 4 buckets. * fix(review): exempt unambiguous id-less first-round originals from the overlap drop (#9208) buildLedger assigns first-round ledger ids positionally without writing them into the posted comment bodies, so a first-round original carries no id token. The carried-id re-post exemption keyed on the body token then failed to match and the re-post was dropped again — the exact regression the gate exists to prevent, preserved for the id-less-original class. Exempt the re-post when the target is unambiguous: the own-account comment is TRULY id-less (no carried id at all, so it cannot belong to a different finding), exactly one carried finding anchors at the location, and exactly one own-account comment sits there. A comment carrying some OTHER id is a different finding thread and keeps the strict match; ambiguous cases (several id-less comments or several carried ids at one line) keep the strict body match too, staying dropped and visible in the drop log. The per-account gate and the cross-account/80-char cases landed in the prior commit stay as-is. Tests: replace the now-obsolete drop pin with an unambiguous-exemption case and an ambiguous-strict case (69 passed). * fix(review): anchor carried-id readback, harden id-less fallback Round of fixes for the carried-id re-post exemption (#9212 review): - extractCarriedIds now mirrors compose-review's prefix readback (severity marker stripped, claim-line prefix only) instead of a \\b-bounded whole-body scan; cross-references and hyphen-run tokens no longer match as carried ids - the id-less fallback requires NO id-shaped token anywhere in the body (ANY_CARRIED_ID, unbounded on purpose: _ is a word character), so a lone comment merely referencing an id cannot ride the fallback - own-account ambiguity count includes replied-to originals - findings-file contract: ids belong on carried-forward findings only; fresh findings of the round omit id - CommentSummary exposes user so authorship-refused exemptions are self-explanatory in the report - SKILL.md Known-limitation narrowed to the ambiguous residue; body-Critical placeholder entries carry no id; sibling wordings aligned - test pins: prefix anchoring, mid-body/underscore/hyphen-run tokens, multi-finding and empty-login fallback gates, prior-repost target, replied-to count, anchored id-shape table rows * fix(review): share carried-id readback, strip markers via constants (#9212) * test(review): kill the five surviving presubmut mutants (#9212) Round-4 test-efficacy probes left five mutants alive at head; each now has a decisive fixture: - repost gate guard: unknown login + author-less comment carrying the id must not match (forcing currentUserLogin !== '' true now fails) - count-loop commit filter: a stale-SHA own comment at the location must not inflate the id-less-fallback ambiguity count - count-loop login filter: another Qwen account's comment at the location must not inflate the count either - case-insensitive authorship: a case-variant own login rides the id-less fallback, which passes through BOTH toLowerCase sites - full-body extraction: padding after the marker pushes the id-led claim line past the 80-char summary slice; extraction must read the full body All five mutants re-probed and killed; 86/86 green; eslint clean. * fix(review): share the carried claim-line strip across readback ends (#9212) The id half of the carried-id readback has been shared since the LEDGER_ID_READBACK hoist, but the marker strip feeding it was still hand-duplicated across presubmit's extractor and buildLedger's titleOf chain, and the copies diverged on unmarked bodies (presubmit read them back, buildLedger skipped them). Hoist the strip as carriedClaimLine next to severityOf: marker choice, slice, colon/whitespace strip, first line, one statement for both ends. The read sides now agree that an unmarked body carries no claim line, the strict direction: submit refuses to post unmarked findings, so an unmarked body is never a re-post. Pinning fixtures: - LEDGER_ID_READBACK table test covering every tolerated terminator plus the hyphen-run and ^-anchor rejections (dropping '.' from the class survived both consuming suites; now it is the sole failure) - colon-right-after-marker shape on the presubmit side, previously pinned only on the compose side - unknown-login count guard: an author-less comment must not ride the degenerate '' === '' comparison into the id-less fallback - unmarked body leading with an id-shaped token stays a plain overlap (fails on the pre-round tree, pinning the unified decision) * docs(review): close the repost-bucket doc gaps + annotate the equivalent guard mutant (#9212) - presubmit: annotate the unknown-login count pre-pass guard as a deliberate short-circuit — its count is consumed only inside the repost gate, which itself requires a known login, so the guard-forcing mutant is provably equivalent (R6-7 round-8 agreement: keep as defense in depth, retire the surviving-mutant metric for it) - DESIGN.md: the convergence paragraph still asserted the Overlap check blocks the exact scenario now exempted as repost; spell out the #9208 exception - SKILL.md: the AMBIGUOUS-only residue enumeration omitted the third conjunct — an id-less original whose body mentions ANY ledger-id token stays excluded from the fallback even at an unambiguous location --------- Co-authored-by: yiliang114 <yiliang114@users.noreply.github.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
a6c2e678c3
|
fix(serve): redact skill bodies from the Web Shell event surface (#9235)
* fix(serve): redact skill bodies from the Web Shell event surface (#9234) available_commands_update snapshots embed every installed skill's full SKILL.md body for ACP clients (e.g. desktop), but no SSE/REST consumer reads them — with many skills installed each snapshot weighed ~640 KB that every browser tab parsed and discarded, pushing the renderer into sustained memory pressure until the tab crashed. Strip _meta.availableSkillDetails at the SDK/browser egress points (SSE frames and the session-load replay arrays); the /acp surface keeps delivering the full snapshot. * fix(serve): redact skill bodies from branch/side-task responses too (#9234) Review follow-up: POST /session/:id/branch and POST /session/:id/side-task serialize the same replay snapshot shape as load/resume but were missed by the redaction. Wrap both 201 responses the same way, harden the load test so a dropped liveJournal redaction can no longer pass silently, and pin the /acp surface's verbatim retention of availableSkillDetails with a mirror test. * fix(serve): harden skill-detail redaction per review (flat frames, envelope pins) - R2-1: recognize the persisted-transcript flat frame shape (data.sessionUpdate) in addition to the eventBus-wrapped shape, with a regression test using a flat frame in compactedReplay. - R3-1: apply the replay-array redaction unconditionally in the branch route instead of re-deriving the bridge's variant discrimination. - R3-2: pin the full frame envelope and response sessionId in the redaction tests so envelope-level regressions cannot ship green. - R3-3: add a colocated skill-details-redaction.test.ts unit suite. * fix(serve): close remaining skill-body egresses per review (#9234) - Redact the virtual-subagent load replay (same BridgeEvent[] shape as the load response). - Redact flat persisted-transcript frames on both transcript routes, where the flat shape is actually produced. - Add regression tests for both paths and the Apache-2.0 header. * fix(serve): tolerate transcript pages without events in redaction (#9234) Guard the transcript-route redaction with `?? []` so a page payload that omits `events` (as some test fakes produce) no longer throws a 500. |
||
|
|
cd54a50d25
|
refactor(goal): type the limit that stopped a Goal (#9165)
* refactor(goal): type the limit that stopped a Goal `reduceGoalResume` decided whether a `usage_limited` Goal may resume by comparing `lastReason` against two exact sentence constants. That made a display string load-bearing: any future path that wants to raise a limit and resume — a budget the user increases, an evidence catalog the runtime salvages — would have to reproduce the prose byte for byte or fail silently against a guard that compares English. `GoalRecord` now carries `limitKind`, written beside `lastReason` wherever the runtime stops a Goal at one of the enumerated bounds, and the resume guard reads that. `lastReason` keeps its current text and stays the human-readable half. Goals persisted before this field existed still refuse to resume through the sentinel comparison behind the new check, so recovery from an older transcript is unchanged. No behavior change: the same Goals resume and the same Goals refuse. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * test(core): cover Goal limit classification * refactor(goal): carry limit kinds structurally * fix(core): reject inconsistent Goal limit snapshots --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com> |
||
|
|
d74f282dc0
|
fix(goal): summarise the last Goal when a turn holds no permit (#9164)
* fix(goal): summarise the last Goal when a turn holds no permit
A Goal that reaches a terminal status stops issuing turn permits, so every
later `get_goal` answered a bare `{ "active": false }`. The run's own turn
count, elapsed time and stop reason became unreadable at exactly the moment
someone needed them — a session that ended in `usage_limited` could not report
how many turns it had completed or why it stopped, and the answer had to be
reconstructed from the assistant's own per-turn narration.
The runtime already holds that record and reading it needs no permit, so
`get_goal` now reports `lastGoal` alongside `active: false`: goalId, revision,
status, turnCount, activeTimeMs and lastReason. Scalars only — the objective
and the evidence checkpoint stay behind the permit, and a session with no Goal
or with unreachable Goal persistence still answers the bare `{ "active": false }`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
* fix(core): narrow last Goal summary
* fix(core): preserve unpermitted Goal summaries
---------
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: qqqys <266654365+qqqys@users.noreply.github.com>
|
||
|
|
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> |
||
|
|
2610f6ed6d
|
feat(review): apply the huge round reduction only when the run has a clock (#9203)
* feat(review): let an operator lower the reverse-audit round cap The round cap is chosen from the diff topology, which is right for the cost question it answers but leaves no way for someone paying for reviews to say "spend less on this loop". `review.reverseAuditRounds` is that knob: a standing operator policy that lowers whichever tier applies. It can only lower, and the asymmetry is the point rather than caution about configuration. A single operator-chosen count is exactly what tiering removed — a round is one agent on a small diff and about ninety minutes on a huge one, so one number is wrong for at least one topology, and most wrong for the one whose cap exists to stop six-hour reviews that post nothing. Lowering carries no matching hazard: it can only end the loop sooner. The floor stays at the huge tier's three, for the reason the plan reader already refuses one and two — a cap below that pre-empts the two-consecutive-dry rule and buys a capped verdict rather than a cheaper review. The two things an operator means by "let it run longer" both have direct expressions that a round count only approximates: a ceiling longer than the huge tier assumes is a deadline, which the admission gate already prices a round against, and "keep going while it is still finding real defects" is a property of the findings rather than of a number chosen before the review starts. It is a setting, not a flag, and it resolves in the capture command rather than at the gate — so it lands in the plan and every reader sees one number without learning a setting was involved. That satisfies the module's standing rule that a budget the caller passes is a budget the caller can inflate, rather than making an exception to it. The reader needs no new code: a lowered value is inside the tier's band, which the existing clamp already honours. Operator scopes only, like the other review policy settings — a repository must not choose how deeply the pipeline verifies it, which the new tests pin by putting the setting in a workspace file and asserting it does nothing. Two things fall out of adding a fourth caller, both fixed here: - The plan builder now takes the ceiling as a REQUIRED parameter. Three capture commands build a plan, an optional one is a parameter a call site can quietly omit, and a setting that applies to two of the three review entry points is worse than one that applies to none. Passing undefined is how a caller says "no ceiling", visibly. - Reading the settings can throw: loading raises a fatal error when any settings file fails to parse, and this is now read while the diff is being captured — the review's first step. A stray comma in a file none of these settings had to come from would have ended the whole review. It degrades to the defaults and discloses instead, and every default is the conservative side: attribution on, no auto-posting, no effort or round-cap override. * chore(review): regenerate the settings JSON Schema for the new review setting The schema file is generated from the settings schema and checked in; adding a setting without regenerating it fails the CI check that keeps the two in step. No behaviour change — the file is derived output. * fix(review): stop the settings degrade from being killed by its own announcement The try/catch added here exists so a corrupt or unreadable settings file cannot end a review: loading throws a fatal error when any settings file fails to parse, and this is read while the diff is being captured, the review's first step. It then announced the degrade through the *throwing* stderr helper, so the announcement could end the review the degrade was written to save. Both halves are reachable together, and the second is ordinary rather than exotic: `process.stderr.write` throws on EPIPE or a closed fd — whenever the reader goes away (`qwen … | head`) or a daemon redirects its stderr — which is why the safe sibling exists and says so in its own docstring. With a broken settings file and no reader, the throw propagates out of the catch and all three capture commands crash before writing a plan. Switched to the safe writer, with the reason recorded at the call site. The test that covers it needed the mock repaired first, because the mock was hiding the bug: it mapped both writers to one non-throwing spy, which makes the throwing and safe helpers interchangeable and mocks away the entire distinction the degrade depends on. The safe one is now a spy that swallows what the underlying write throws, matching the real contract — so a test can make the write fail and see which helper the code chose. Reverting to the throwing helper turns the new test red; before this, it turned nothing red. * fix(review): correct two rationales that contradict the code they sit beside Both are claims about mechanism, both false, and both contradicted by documentation already in the same file — which is what makes them worth fixing past the round where only correctness fixes land: in this codebase the comment is the design record, and a wrong one outlives the round that shipped it. **Why a cap of one or two is refused** was stated in four places as "it forces a non-converged stop where two-consecutive-dry would have converged on its own". That is false for two: the convergence check runs before the cap gate, so an all-dry loop reaches CONVERGED under any cap of two or more — which the huge-tier constant's own docstring, two functions away, already said. The reasons are real but different for each value. One refuses the convergence pair's second member, so the loop can never produce the two dry audits convergence is defined by. Two lets an all-dry loop converge but leaves no round for a loop that reports anything, so the first finding makes the stop non-converged. Both end in a capped verdict rather than a cheaper review; only the mechanism was wrong. **When loading settings throws** was described as "any settings file fails to read or parse", with a stray comma as the example. Malformed JSON is the one case that does not throw: it is copied aside and recovered, under a comment that says "Never crash due to a corrupted settings file". The throw comes from a file that cannot be read, which is enough on its own to justify the degrade — this is read while the plan is being captured — so the correction narrows the claim without weakening the reason for the guard. * feat(review): apply the huge round reduction only when the run has a clock Three is the one tier lower than the topology beneath it, and read as a statement about auditing it is backwards. A huge diff has more defects and more territory than a chunked one, converges later, and on recall deserves more rounds rather than fewer — the standing counterexample is a 5,801-line PR that took eight review rounds and was still surfacing Criticals in code present since its first commit. It was never a statement about auditing. It is a statement about a wall: a reverse-audit round on a 4,000-line PR is about ninety minutes, five of them are 450, and a six-hour CI ceiling does not hold that plus the fan-out and the tail. The survey behind it measured absent reviews rather than slow ones — twenty-six timed-out jobs in one window, about 122 hours of compute, nothing posted. Three rounds reported beat five rounds lost. That argument is sound exactly where the wall is. A local run exports no review deadline, nothing kills it at six hours, and the reduction there trades recall away to fit a ceiling that does not exist — on the tier where recall matters most, and by a number calibrated against somebody else's CI. So the reduction now applies only when the run has a deadline at all. With a clock a huge diff caps at three, as before; without one it is simply a large chunked diff and caps at five. The clock is read where the settings ceiling is read — in the capture command, passed into the budget, recorded in the plan — so the two facts the cap depends on arrive the same way and the budget module keeps its property of touching neither the environment nor the settings. Both now travel as one context object rather than a growing parameter list, still required at the plan builder so a capture command cannot silently omit them. The admission gate asks the same question through the same parse the deadline gates already use, so "has a deadline" and "a deadline will be enforced" cannot come apart. All four capture/gate clock combinations are safe and covered: a plan captured without a clock records five and is honoured at five; read later under a clock its band closes to three and it is cut to three, which is the conservative direction when a wall turns out to exist after all. Two things this does not pretend to fix, both recorded in the design note. The deadline gate falls back to a flat thirty-minute round estimate until it has measured one, so on exactly the runs that time out it under-prices the first two rounds threefold and cannot refuse them — that, not the round count, is why a static reduction was needed on top of a working gate, and a size-aware first estimate is what would retire the reduction entirely. And chunk retirement can only begin at round three, so under a three-round cap only one round can ever shrink: the arithmetic that justifies the cap is an arithmetic the cap guarantees stays true. * fix(review): correct the claims this stack got wrong, and cover its untested seams Round-4 review of the three stacked changes, all of it comment-and-coverage rather than behaviour. Grouped by what was actually wrong. **A doc comment detached from what it documents.** The context interface was inserted between `reviewBudget`'s doc block and the function, so the whole block — including the input-laundering contract that opens it — attached to the interface and the function was left undocumented. The interface moves up beside its sibling with its own doc; the function keeps its contract. Its new paragraph also called both context fields environment values, when only one is — the other is resolved from settings. **Four test comments that argued for their assertions with false reasons.** The assertions were right and the rationales were not, which in a codebase whose comments are its design documentation is the more durable error. The integer guard is tested before the floor, not after, so the fractional value cited says nothing about what the floor would have caught alone. Not every value in the coercible-garbage list becomes zero — a negative stays negative and two strings become real counts, one of them large enough to land on the huge tier rather than the fallback. A single global bound of ten honours only two of the three clamp assertions, not all three; the third is an edge case, not a discriminator. And one comment justified its cases by naming a writer that does not exist on that branch. **A guarantee that is not guaranteed.** The settings fallback was documented as always degrading toward more work; that holds for three of the four fields and not for effort, where losing an operator's `high` returns the built-in rule and a local review drops to medium — less work, not more. Named as the exception it is, which is also why the fallback discloses on stderr. **Five prose sites that never learned the cap is conditional.** The step's stop rule, the clock module's own header, the large-diff cost model, the setting's schema description and the user-facing feature doc all still enumerated the cap unconditionally, contradicting the change one file away. The schema description additionally advised setting a deadline "to let a productive loop run longer", which is backwards: on a huge diff a deadline lowers the cap from five to three. **Two prose sites that overstated scope.** The setting is honoured from three operator scopes, not user only, and a value below the floor is ignored rather than clamped to it; and it cuts the cap for high-effort reviews only, since medium skips the reverse audit and low runs none. A cross-reference named a heading that does not exist. **The two seams nothing covered.** The environment boundary — whether this run has a deadline — was never exercised with the variable actually set: the budget tests pass the flag as a literal and the gate tests delete it. The operator ceiling's write path was likewise untested end to end; every builder call in the unit tests passes it as absent. Both now run through the real capture handler against a real environment and a mocked settings source, and both were mutation-checked: inverting the environment predicate and ignoring the ceiling each turn the new tests red. * fix(review): finish propagating the clock, and close the seams round 5 found Round-5 review of the stack. One real defect, one unsafe writer, and the rest is narration that stopped short plus coverage that could not see the change it was meant to cover. **A mock that leaked into every test after it.** The handler test added last round set an operator ceiling on a module-level settings mock and never restored it, so every later test in that file — including the whole trailing block — ran the real handler with an undeclared ceiling of twenty in play. Inert today because nothing downstream asserts on it, which is exactly how it would have survived to matter. Reset in the file's `beforeEach`. **The degrade announced itself with a writer that can destroy the degrade.** The settings fallback wrote its NOTE through the throwing stderr helper, whose safe sibling exists for precisely this case and says so in its own docstring: the write is incidental to the work in hand, and failing it would take down the fallback the guard exists to provide. Switched, with the reason recorded. **Five narration sites still stated the huge reduction unconditionally** — the field doc, the cap reader's own bullet, the enforcement-site comment above the round gate, the skill's version-skew paragraph, and the design note's "what an operator means by let it run longer". The last one was the most wrong: it named a deadline as the way to say "my ceiling is larger than six hours", but the check is for a deadline's *presence*, so any deadline however generous reads as three, and the cap is evaluated before the deadline arithmetic — setting one lowers the cap rather than raising it. Saying that would need the tier to read the deadline's size, which is now written down as the missing capability rather than implied to exist. **The setting was documented everywhere except the reference table.** The hand-maintained settings reference lists the review section exhaustively and did not list this one; the generator only writes the JSON schema, so it does not self-heal. Its schema description also never mentioned that only whole numbers are honoured, which matters because JSON Schema has no integer type here — a fraction validates in an editor and is discarded at runtime. **Two coverage gaps where the change was invisible to mutation.** Every gate test used the unsized fixture, whose tier is the fallback whatever the clock says, or forced a cap by storing one — so hardcoding the clock argument at all four call sites left the suite green. A sized huge plan is the only shape where the flag decides anything, and it now drives the gate on both sides. And two of the three capture commands had no budget assertion at all, so dropping either context field from their call sites compiled clean; the local one now asserts both. Both new tests were mutation-checked against exactly those edits. |
||
|
|
f9bc8cb250
|
fix(autofix): re-anchor growth divergence on measurement time and external head moves (#9192)
* fix(autofix): re-anchor growth divergence on measurement time and external head moves Tightens the growth-divergence comparability window (PR #9104 follow-up, tracked as #9114): - measured_at (R2-6): the growth-now marker now carries the prepare-time measurement instant, and the divergence read filters on it instead of the comment's created_at. The report posts the marker only after the agent's ~120-minute run, so a round in flight when a concurrent base update landed would otherwise pass a created_at filter while carrying sums measured against the old base. - external head move (R2-8, subsumes R6-3): prior sums are measured against origin/main, so any commit an external actor (author push) or a stale-base merge added since the bot last evaluated the branch inflates this round's sum relative to them. BASE_UPD_AT only tracks the bot's own update-branch merge; the new GROWTH_NOW_CUTOFF also re-anchors (drops all prior sums) whenever the checked-out head is not the bot's last judged head (LIVE_RED_HEAD), covering author pushes and base updates alike. The reader now dedups/orders per run by measured= (a re-run's fresh measurement wins). Contract tests cover the measured-based cutoff, the external-head-move re-anchor (both branches), and the writer→reader round-trip with the new field. 172/172. R6-6 (markers don't store the effective budget, so a mid-window budget raise counts old rounds against the new regime — fail-safe, one round early) stays tracked in #9114. * fix(autofix): drop the head-move re-anchor, keep the measurement-time filter Review found the head-move half of this change broken in three ways (all probe-verified), so it is withdrawn and returned to #9114 rather than patched under review: - R1-1 (regression): `autofix-redcheck` records the head the agent was GIVEN, frozen before its push — so after any pushing round the next round's head differs and the cutoff was set to now, dropping every prior sum. In the push regime OVER_ROUNDS_PRIOR could never reach the threshold and the #9104 handoff would never fire at all. - R1-2: the cut was stateless — the round after a correct re-anchor fell back to an empty cutoff and re-admitted every pre-move sum. - R1-3: with no redcheck marker (a crash round) the `-n` guard skipped re-anchoring across a genuine external move. A correct version needs both a bot-authored-move test and a PERSISTED cut; that is its own change. What remains is the measurement-time filter (R2-6), which stands on its own: the marker carries the prepare-time instant and the divergence read filters/orders on it instead of the comment's post-agent created_at. Also from this review: - R1-4: `measured=` is OPTIONAL in the scan, falling back to the comment's created_at, so deploying does not blank an in-flight window's census. - R1-9: the per-run collapse now runs BEFORE the over/window/cutoff filters — a re-run whose fresh attempt came back under budget was still represented by its stale over=true attempt. - R1-7: comments corrected — run= is the DEDUP identity, measured= the ORDER key (four sites). - R1-8: recorded as a known residual next to the sibling growth-base reader, which still filters on created_at; tracked in #9114. - R1-5/R1-6: fixtures decouple created_at from measured=, cover a legacy marker (with and without the cutoff), and pin the measured_at source line in prepare. * fix(autofix): keep the failure-path growth marker scannable when prepare never ran * fix(autofix): prefer explicit measured= over created_at fallback in the per-run growth collapse * test(autofix): pin the explicit-measured preference in the per-run collapse |
||
|
|
6f3b583701
|
fix(review): give duplicate-dropped Suggestions their own compose state and body sentence (#9215)
* fix(review): give duplicate-dropped Suggestions their own compose state and body sentence A review whose confirmed Suggestions were dropped because the PR already carried them (prior round or concurrent reviewer) had nowhere to record the drop except suggestionsDiscarded — whose body sentence asserts an anchor failure. On #9204 resolve-anchors returned three exact-added matches, the drop reason was duplication, and the posted body claimed the findings "could not be anchored to a changed line" — a public claim the run's own artifacts contradict. Add a suggestionsDroppedAsDuplicates state field: entries name the finding and where it already lives, render as their own body paragraph (bilingual, comment refs linkified), and count toward S exactly like anchor-failure discards so an all-duplicate run never reads as zero-finding. The skill's Step 7 state list now routes duplicate drops to it instead of the count. Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * fix(review): hoist plan PR-identity read; pin duplicate-clause zh count and empty-entry filter * fix(review): duplicate-drop account renders on every event; one stripped-list helper * fix(review): cap-free duplicate-drop fixtures, honest seam pin, body-rule carve-out * test(review): pin the full duplicate list and the compose-review --input seam * fix(review): bound the duplicate-drop account like the deferred channel * fix(review): drop truncated comment refs, collapse CRs, bound cannot-tell (#9215) --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> Co-authored-by: qwen-code-dev-bot <qwen-code-dev@service.alibaba.com> |
||
|
|
b29549ade8
|
feat(review): scale the reverse-audit round cap to the diff topology (#9183)
* feat(review): scale the reverse-audit round cap to the diff topology The reverse-audit loop's round cap was one number for every topology, but what it bounds is a round — and a round costs one auditor on a small diff, one auditor per non-retired chunk on a chunked one, and about ninety minutes on a huge one. Two orders of magnitude across the topologies a single cap had to serve, so it was necessarily wrong at one end: too loose to bound the huge case, which is why a reduced cap had to be carved out of it, and tight enough on a small diff to stop loops that were still confirming Criticals — for a saving of about five calls out of a seventeen-to-twenty-three-call review. The asymmetry this design is built on says that is the wrong trade: a missed issue costs another review iteration, and per-run cost is the cheaper side. The cap now comes from the plan's topology tier — ten on a small diff, five on a chunked one, three when huge. The huge tier is checked first and wins, since it is a finishability ruling; a small diff can never reach it anyway, because the effective-line measure is bounded by the topology gate itself. Two supporting moves. The topology gate moves into the budget module, so the roster and the round cap read one predicate instead of two copies of the same pair of numbers. And the cap reader now takes the whole plan rather than its budget alone, so a stored value is clamped into that plan's own tier: a plan written before tiering gets the rounds its topology earns instead of another topology's number, and a hand-edited plan cannot buy a tier it did not qualify for. A plan carrying no usable size reads as the chunked tier — what every plan got before this change — because an unsized plan could be the large one. Nothing else about the loop changes. It still ends on two consecutive dry rounds, a cap stop is still a non-converged stop that writes its marker and caps the verdict, and in a time-budgeted run the deadline gate remains the operative bound. * fix(review): size the round tier from real numbers, not coercible garbage Round-2 review of the tiering change found the tier's usability check does the opposite of what its own comment promised. It coerced first and asked `Number.isFinite` after, and `Number()` turns `null`, `''`, `false` and `[]` into a finite zero — so a plan whose sizes are unknowable was read as a zero-line diff and handed the small tier's ten rounds, the most expensive cap, while the sibling case of an entirely absent field correctly fell back to five. That shape is not hypothetical: JSON serialization writes a not-a-number line count as null, so the corrupted plan the fallback exists for arrives looking exactly like an empty diff. A numeric string coerced too, which would have let a hand-edited huge plan reach the small tier through the very clamp added to stop it. The write path had the same hole from the other side: it sized the tier from counts already laundered to zero, recording ten rounds into a plan whose size never arrived, where the flat cap recorded five. Usability is now judged before coercion — a real, finite, non-negative number — and the write path hands the tier its raw input so a garbled count stays garbled instead of arriving as a legitimate zero. Zero itself still reads as what it is: an empty diff is a small diff, not an unsized one. Also from that review, all confirmed against the code: - The cap reader's comment claimed two things the code never did. It does not migrate a legacy small plan to ten — a pre-tiering CLI wrote five, five is inside a small plan's band, and a value the plan states is honoured; only an absent or out-of-band one reaches the tier. And it does not always err toward more auditing — a field-less huge plan now reads three where the flat fallback read five, deliberately less, because that tier is a finishability ruling. Both claims are replaced by what happens, and both are now pinned by tests rather than asserted in prose. - Two code comments still narrated the old two-value cap; the same sweep that updated every other prose site had missed them. - The garbled-value test never reached the integer guard: the lower bound rejects a fractional value below it first, so deleting the guard left the suite green and a fractional cap inside the band would have been honoured. - The design note's fork-subagent section had its call count updated but every figure derived from the old count left behind, so the section contradicted itself; the derived token figures are re-derived from the new range. - The effective-line measure was written twice in one module, which is two size measures that can drift apart inside one budget object. - The plan interface the cap reads through declared none of the three fields it now reads, so a rename on the writing side would compile clean and collapse every cap to the fallback tier in silence. * test(review): pin the round cap's band against a constant-bound mutation Every existing case for the stored-value clamp sits on a boundary — at the floor, at the tier, or outside it — and a boundary-only suite cannot tell a tier-relative bound from a constant one. Mutating the upper comparison from the plan's tier to the flat large value passed all fifty-seven tests, which means a later simplification back to one global bound would ship green while changing behaviour on exactly the plans the clamp exists for. The interior is where the two bounds differ, and it is reachable without a hand edit: the operator round ceiling writes such a value into the plan. Three assertions cover it from both sides — a small plan storing seven keeps seven rather than collapsing to its tier, and a huge plan storing four is still cut to three rather than being handed a round past its finishability tier. * fix(review): close three consistency gaps found in round 3 All three are about this change disagreeing with itself rather than about behaviour a user would see today. The tier function derived the same two line counts twice: once for the huge-diff gate, from validated locals, and again for the topology gate, by re-reading the raw object. They agree today, and they agreed right up until the last round found a defect of exactly that shape — one derivation laundering garbage the other rejected. The topology gate now takes the validated pair. The skill's version-skew paragraph still carried the slogan the code comment retracted a commit ago: that a plan with no budget field errs toward more coverage, never less. It does for the four fields it lists, and it does not for the round cap — a field-less huge plan reads three where the flat fallback read five. The paragraph now says so instead of asserting the opposite of the rationale two files away. Two of the four call sites that read the cap from the plan had no test at the new ten-round tier: the per-chunk build gate and the retirement note. Both are reachable on a small plan, which can carry chunks — the chunk budget is 400 lines while the small-diff gate admits 3200. The retirement-note case is the sharper of the two, and it is the same scenario as its cap-five sibling with the opposite outcome: a round-five retirement schedules a cold check for round six, which the small tier allows, so the note must promise that check rather than close the certificate. |
||
|
|
43b0779bcf
|
fix(telemetry): Address main agent tracing edge cases (#9121)
* codex: address PR review feedback (#9107) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#9121) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#9121) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#9121) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#9121) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#9121) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#9121) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> * codex: address PR review feedback (#9121) Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
5677823abb
|
fix(review): note when --all-chunks fans out a plan whose numbers say 3A (#9249)
* fix(review): note when --all-chunks fans out a plan whose numbers say 3A runAllChunks gates on requireAuditableChunks alone and never consults the topology gate, so --all-chunks can fan out one auditor per chunk on a plan whose own srcDiffLines/diffLines say Step 3A — one whole-diff auditor per round, which is what the reverse-audit round cap is priced for (#9242). Add a purely diagnostic stderr note after requireAuditableChunks (no exit-code change, no refusal — legitimate repair paths exist), and record the decision in SKILL.md next to the topology gate and the Step 5 builder paragraph so the orchestrator explains a deliberate fan-out instead of letting the mismatch ride silently. * fix(review): keep the 3A topology note below the gates and on both fan-out paths Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> --------- Co-authored-by: yiliang114 <yiliang114@users.noreply.github.com> Co-authored-by: Shaojin Wen <shaojin.wensj@alibaba-inc.com> Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
3119d53e4d
|
fix(sdk): raise daemon browser bundle budget to 191KB (#9238)
Some checks are pending
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:docker - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 1/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 2/3 (push) Waiting to run
E2E Tests / E2E Test (Linux) - sandbox:none - shard 3/3 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 1/2 (push) Waiting to run
E2E Tests / E2E Test - macOS - shard 2/2 (push) Waiting to run
E2E Tests / channel-plugin E2E (nightly) (push) Waiting to run
E2E Tests / cron-interactive E2E (nightly) (push) Waiting to run
E2E Tests / web-shell Browser Regression (push) Waiting to run
SDK Java / ubuntu-latest / Java 11 (push) Waiting to run
SDK Java / ubuntu-latest / Java 17 (push) Waiting to run
SDK Java / macos-latest / Java 21 (push) Waiting to run
SDK Java / ubuntu-latest / Java 21 (push) Waiting to run
SDK Java / windows-latest / Java 21 (push) Waiting to run
SDK Java / Real daemon E2E / Java 11 (push) Waiting to run
Security Checks / Dependency CVE audit (push) Waiting to run
Security Checks / Secret scan (TruffleHog) (push) Waiting to run
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |
||
|
|
60a7bda120
|
fix(integration-tests): ack daemon tool-guard handshake in the mock ACP child (#9159) (#9161)
* fix(integration-tests): ack daemon tool-guard handshake in the mock ACP child (#9159) The cross-worktree Git guard made the daemon's built-in tool guard unconditional, so the bridge now refuses any ACP child that does not acknowledge the required guard handshake during initialize. The mock ACP child used by the live-journal recovery E2E tests never acked, failing every session it served with "ACP child did not acknowledge the required external tool guard" on all E2E platforms and sandbox legs. Mirror the production child contract in the mock: consume the private guard marker and return the ready acknowledgment in the initialize response when the daemon requires the guard. * fix(integration-tests): ack session close ext method in the mock ACP child (#9159) * fix(integration-tests): keep mock ACP child lightweight and gate its close ack (#9159) * fix(integration-tests): narrow close-gate comment to what it catches (#9159) * fix(integration-tests): name the post-merge E2E workflow in the close-gate comment (#9159) --------- Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com> |