qwen-code/.github/scripts
Shaojin Wen 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 (&#64; &#x40;
  &#0064; &commat;) 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.
2026-08-16 11:28:05 +00:00
..
ci perf(ci): run docs-only automatic reviews at medium effort (#8648) 2026-08-07 17:00:32 +00:00
dsw-swe-verified ci: add isolated DSW SWE-bench release pipeline (#7656) 2026-07-29 06:35:58 +00:00
assign-issue-owner.mjs feat(ci): auto-assign issues to area owners from labels (#8668) 2026-08-08 23:01:03 +00:00
assign-issue-owner.test.mjs feat(ci): auto-assign issues to area owners from labels (#8668) 2026-08-08 23:01:03 +00:00
auto-minimize-spam.test.mjs fix(ci): minimize spam inline review comments (#9229) 2026-08-15 14:33:17 +00:00
cap-release-notes.mjs fix(release): keep notes anchored and cap the release body (#8199) 2026-07-31 09:55:38 +00:00
cap-release-notes.test.mjs fix(release): keep notes anchored and cap the release body (#8199) 2026-07-31 09:55:38 +00:00
check-autofix-contracts.sh ci(autofix): add cross-package contract verification (#7642) 2026-07-24 05:13:04 +00:00
check-settings-schema.sh ci(autofix): recover from generated-artifact CI gates and stop silent stalls (#6998) 2026-07-17 03:26:43 +00:00
ci-flaky-rerun.mjs feat(ci): auto-open a deflake fix issue for confirmed flaky tests (#7231) 2026-07-19 16:49:29 +00:00
ci-runner-routing.test.mjs ci: route trusted-author fork PRs and no-checkout jobs to the ECS pool (#8502) 2026-08-04 03:48:24 +00:00
classify-release-notes.mjs fix(ci): route workflow label mutations through REST (#8761) 2026-08-09 15:05:15 +00:00
classify-release-notes.test.mjs fix(ci): route workflow label mutations through REST (#8761) 2026-08-09 15:05:15 +00:00
create-desktop-update-manifest.mjs feat(desktop): add Aliyun OSS release mirror (#8976) 2026-08-12 10:59:10 +00:00
create-electron-bridge-manifest.mjs fix(desktop): bridge Electron users on Windows and Linux (#9079) 2026-08-13 15:42:31 +00:00
pr-safety-precheck.mjs fix(ci): limit fork PR precheck to safety signals (#6178) 2026-07-02 20:56:41 +08:00
pr-safety-precheck.test.mjs fix(ci): limit fork PR precheck to safety signals (#6178) 2026-07-02 20:56:41 +08:00
qwen-triage-workflow.test.mjs fix(ci): stop dropping agent settings in resolve and follow-up workflows (#9252) 2026-08-16 03:27:00 +00:00
resanitize-git-config.sh fix(ci): make autofix verification gates hermetic to runner git config (#8961) 2026-08-13 11:39:04 +00:00
resolve-owning-packages.sh fix(autofix): resolve owning package for nested paths; report verify-failed handoffs as not pushed (#7330) 2026-07-20 14:39:56 +00:00
resolve-sandbox-image.mjs ci(autofix): restore sandbox image flow (#6261) 2026-07-03 15:30:58 +00:00
resolve-sandbox-image.test.mjs ci(autofix): restore sandbox image flow (#6261) 2026-07-03 15:30:58 +00:00
run-autofix-review-verification.sh feat(autofix): deny-by-default footprint gate and positional window censuses (#9156) 2026-08-14 17:28:16 +00:00
serve-ab-diff.mjs ci(serve): daemon A/B before/after preview on response-surface PRs (#6975) 2026-07-16 00:58:52 +00:00
serve-ab-diff.test.mjs ci(serve): daemon A/B before/after preview on response-surface PRs (#6975) 2026-07-16 00:58:52 +00:00
serve-ab-drive.mjs ci(serve): daemon A/B before/after preview on response-surface PRs (#6975) 2026-07-16 00:58:52 +00:00
upsert-bot-comment.sh perf(ci): run docs-only automatic reviews at medium effort (#8648) 2026-08-07 17:00:32 +00:00
upsert-bot-comment.test.mjs perf(ci): run docs-only automatic reviews at medium effort (#8648) 2026-08-07 17:00:32 +00:00
upsert-deferred-issue.sh feat(autofix): defer verified out-of-footprint findings to a surviving follow-up queue (#9189) 2026-08-16 11:28:05 +00:00
web-shell-visuals-compose.mjs ci(web-shell): denoise cross-job font-AA so visual previews stop false-flagging (#7210) 2026-07-19 11:27:08 +00:00
web-shell-visuals-compose.test.mjs ci(web-shell): denoise cross-job font-AA so visual previews stop false-flagging (#7210) 2026-07-19 11:27:08 +00:00
web-shell-visuals-publish.mjs fix(ci): don't let one failing scenario sink the whole visual preview (#7511) 2026-07-23 02:34:07 +00:00
web-shell-visuals-publish.test.mjs fix(ci): don't let one failing scenario sink the whole visual preview (#7511) 2026-07-23 02:34:07 +00:00