feat(dispatch): tell a crashed shell reviewer apart from a slow one (G3) - #84
Conversation
The shell (gemini) branch consulted neither exit code nor stderr. A reviewer that died on launch — bad flag, expired auth, exhausted quota — left a copy byte-identical to its seed, which is byte-identical to what a reviewer still thinking leaves. The primary then spent the exit-9 grace re-run and the whole exit-8 retry budget (up to ~40 minutes) re-waiting on a dead process, and quarantined it as `no turn taken` — the reason for a reviewer that read the document and declined. The cause was on the process's own stderr the entire time and nothing ever looked. The dispatch now redirects both streams to `<doc>.<id>.multi-review.log` and appends `multi-review: dispatch exited $?`. Step 5 reads that log before the first wait and at every bound hit: a non-zero status quarantines immediately with the log's last line as the reason and skips both retry paths; an absent status line means the process is still running and the existing rules apply unchanged; a zero status with an unflipped marker is a genuine `no turn taken` that can now be recorded with evidence rather than inferred. `.multi-review.log` is an already-gitignored shape, so the new artifact does not leak into consuming repos. Three packaging assertions cover it, each with a mutation-table entry. The existing command/gemini-dispatch-cwd-pinned entry is re-pointed at the moved dispatch line, and the cwd test's window is widened from 22 to 30 lines — the log capture had pushed the dispatch line onto the window's last row, where any later insertion would have silently ended its coverage. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nm3hNXA4WHbgKjFbxX7VUE
|
Full mutation sweep: green. Exit 0, no 91 entries, up from 88 on 🤖 Comment posted by an AI agent — Claude Opus 5 ( |
Six findings from PR #84's round 1 (codex gpt-5 + fable), all agreed. The status sentinel shipped as a bare `echo`, which appends onto whatever the process last wrote. A CLI that dies mid-write leaves no trailing newline, so the log ended `quota exceededmulti-review: dispatch exited 1` and no line-anchored read could find the status — disarming the detection in exactly the crash it was built for. Both secondaries found this independently (codex-rd1-r1, fable-rd1-r2); reproduced, then fixed with `printf` and a leading newline, verified against newline-less, newline-terminated, and empty output. A flipped marker now outranks a non-zero status (fable-rd1-r1). A CLI can write its turn, flip the marker, and only then die on teardown or a post-edit call; quarantining on the status alone discarded a completed turn and every finding in it — strictly worse than the bug the log fixes, and reachable the moment the log exists. The quarantine reason names the log instead of quoting it (fable-rd1-r3). The PR's own security note rested on the log staying gitignored and local, then instructed pasting its last line — most likely an auth error — into a field the doc records durably and the gate renders. The dispatch clears the previous round's log before launching (fable-rd1-r4): the redirect truncates only when the background process opens the file, so a pre-wait read could win that race and quarantine a just-launched reviewer on round N-1's status. The rc-zero branch defers to the wait's --seed comparison instead of naming the state itself (fable-rd1-r5): exit 9 is a genuine `no turn taken`, exit 8 means findings were written and the flip forgotten, and a clean exit code distinguishes neither. Four new mutation entries; command/shell-dispatch-exit-status re-pointed, its mutation now restoring the exact `echo` form the reviewers rejected. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nm3hNXA4WHbgKjFbxX7VUE
…indow, not the instruction The r3 fix added `see <doc>.<id>.multi-review.log` to the quarantine-reason line, a second mention of the filename inside the same wait window the log-consulted assertion greps. From that point the assertion stayed green with its target instruction deleted, which the mutation sweep reported as SURVIVED. It now matches the instruction to READ the log rather than the filename appearing anywhere nearby. Third instance of this class in one change: a verification that reads correctly and cannot fail. The mutation table is what caught it, which is the argument for the table. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nm3hNXA4WHbgKjFbxX7VUE
… not the exit code Seven findings from PR #84's round 2 (codex gpt-5 + fable), all agreed. Round 2 was run because round 1's fixes were unreviewed new logic; it found that three of them were wrong. The stale-log removal was relocated, not fixed (fable-rd2-r1). It sat inside the `shell` dispatch block, which runs as a background task — so it raced the primary's own pre-wait read of the same file, the very race it was added to close, while the prose claimed "what you read is always this round's". It now runs in the seeding step, synchronously, before anything is dispatched. The decision table keyed off the exit code, which produced opposite fates for one on-disk state (fable-rd2-r2) and re-waited on processes already known dead (fable-rd2-r3). It is now keyed off the copy: a flipped marker wins outright; a copy that changed before dying is recovered via channel-check rather than re-waited or discarded; only a copy byte-identical to its seed is quarantined. The exit code is evidence that the process ended, and nothing more. The status search takes the LAST matching line (fable-rd2-r4): the log is verbatim CLI output and a reviewer echoing this protocol's own text reproduces the sentinel — which in this repo's self-reviews is not hypothetical. The residual cost is now stated rather than implied (codex-rd2-r1): a process that dies just after the pre-wait read is not noticed until the next bound hit. One bound, against the four this removes. Closing it means teaching multi-review-wait.sh to watch the log, which is a separate change. Both test windows are now bounded by document structure instead of a line offset. Two fixed offsets in a row (22, then 30) left their target on the window's last row, where the next insertion silently ends coverage — fable-rd2-r5 caught the second one, which was itself the fix for the first. Duplicate (c) label fixed (fable-rd2-r6); the block now runs (a)-(i). Two new mutation entries, three re-pointed. 97 total. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nm3hNXA4WHbgKjFbxX7VUE
The round-2 test rewrite located its insertion point with a marker string that occurs three times in the file and took the FIRST match, so it overwrote from the cwd-pin block to end-of-file. That deleted the shell-cwd-pin, the session-root-claim, and the whole empty-argv/DISPATCH-FAILED block. The suite stayed green — what remained still passed — and `--verify-table` stayed green, because the table's target lines are in commands/multi-review.md and those were untouched. Only the full mutation sweep caught it, reporting three SURVIVED and one MISCREDITED. The blocks are restored verbatim from ebf9cd0 and their windows converted to structural bounds, which is what the round-2 commit claimed for "both windows" while these three were sitting deleted. Fourth instance in this PR of a verification that reads correctly and cannot fail. The first was caught by me, the second by two reviewers, the third and fourth by the mutation sweep. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nm3hNXA4WHbgKjFbxX7VUE
Five findings from PR #84's round 3 (codex gpt-5 + fable): four agreed, one disputed. Both reviewers independently found the PR description's summary table still describing the round-1, exit-code-keyed design that round 2 replaced (codex-rd3-r1, fable-rd3-r1). A reader following the summary would reinstate the grace re-run and retry budget the revision removed. The table now matches the shipped bullets. Same cause as the stale-description problem round 2 hit: `pr.sh refresh` updates `## Diff` and nothing keeps the description honest. The sentinel rule is tightened from "last match" to "the log's FINAL non-empty line" (fable-rd3-r2). Last-match only defuses an echoed sentinel once a real one exists; while the reviewer is alive there is no real one, so an echoed line — which this repo's own self-review material supplies verbatim — is the last match and a live reviewer reads as exited. The dispatch writes the real status after the process ends, so being final is what makes it real. Assertion (d)'s window is bounded structurally (fable-rd3-r3). It had reintroduced a fixed offset in the same change that twice removed them. DISPUTED fable-rd3-r4: the recovery path is not blocked by an unflipped marker. Reproduced against both scripts — channel-check exits 0 on an unflipped copy, and merge accepts one and ingests its finding. Raised as `low` because the reviewer could not verify it in scope, which is the correct call; verifying it is the primary's job. The doc now records the result so the question is not re-asked. Also removed a stale cross-reference to "the rc-zero case", a branch the round-2 restructure eliminated. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nm3hNXA4WHbgKjFbxX7VUE
kevin-agrology
left a comment
There was a problem hiding this comment.
Commented inline (3)
Could not place inline (12)
- commands/multi-review.md:487-489 — 🟠 med — The fable-rd1-r4 fix relocates the stale-log race instead of closing it, yet the doc now claims it closed:
rm -fruns INSIDE the background dispatch task, after the argv-building helper, so a pre-wait read can still see round N-1's status line — risk: a just-launched reviewer is quarantined on the previous round'sdispatch exited <rc>— the exact bug r4 raised, now behind prose telling the primary to trust the log unconditionally - commands/multi-review.md:498-502 — 🟠 med — The rc-non-zero bullet quarantines immediately without consulting the seed, discarding a copy that CHANGED — on-disk findings the rc-zero bullet explicitly calls "recoverable rather than discardable" for the identical on-disk state — risk: a reviewer that wrote its findings and died one step before the flip loses its whole turn, the failure mode fable-rd1-r1 was agreed to prevent, protected here only when the death lands after the flip
- commands/multi-review.md:511-515 — 🟠 med — rc-zero + changed copy defers to the exit-8 handler, whose premise — "demonstrably alive and still writing" — the log has just disproven: the primary spends up to 3 more 600s waits on a process it knows exited, then quarantines with the wrong reason — risk: the ~40-minute wait-on-a-corpse this PR exists to eliminate survives in the rc=0 lane, ending in
still writing at the retry capfor a reviewer that stopped writing before the first retry; "recoverable" is never operationalized into an action - commands/multi-review.md:489-490 — 🟡 low — The status sentinel is spoofable by log content: the log is the CLI's verbatim stdout/stderr, "Look for the line
multi-review: dispatch exited <rc>" names no occurrence rule, and in this repo's self-reviews the reviewed material itself contains that exact string (commands/multi-review.md:465,467,489 and any PR diff of them) — a CLI echoing prompt or doc content could plant a false status while still running — risk: a live reviewer is quarantined on a status line its own output quoted rather than the dispatch wrote - scripts/multi-review-packaging.test.sh:559-561 — 🟡 low — The widened
n+30window has zero margin again: theprintfstatus line sits at exactly the window's last row (heading at commands/multi-review.md:437, printf at 467), reinstating the precise condition the widening comment warns about — the next line added above it pushes assertion (b) out of the window (loudly, but avoidably) — risk: the block breaks on the first unrelated insertion above the printf, and the comment's own rationale ("a window with no margin stops covering...") is contradicted by the chosen value - scripts/multi-review-packaging.test.sh:649 — 🟡 low — The new stale-log check reuses the label "(c)" already held by the log-consulted check three comments below, so the block reads (a) (b) (c) (c) (d) (e) — risk: cross-references to these subchecks (mutation-table comments, future findings) become ambiguous
- commands/multi-review.md:456-457 — 🟠 med — Ensure the appended exit sentinel starts on its own line;
echodoes not separate stderr that lacks a final newline. — risk: a crashed reviewer can still be classified as slow - commands/multi-review.md:482-487 — 🟠 med — The non-zero-rc row quarantines unconditionally, without first checking whether the marker flipped — a reviewer that completed its turn and then exited non-zero is discarded with its findings. — risk: a valid completed turn is quarantined as a crash and its findings never merge
- commands/multi-review.md:459 — 🟠 med — The status line is appended with a bare
echoonto whatever the CLI last wrote; a crashing CLI whose final stderr write lacks a trailing newline glues the two into one line, and the "last line ismulti-review: dispatch exited <rc>" test then reads as Absent. — risk: the crash detection disarms in exactly the crash case it was built for — the dead reviewer reads as "still running" and burns the full retry budget again - commands/multi-review.md:483 — 🟠 med — The quarantine reason copies a verbatim log line (
<the last non-empty line above that one>) into the coordination doc, which undercuts the Security section's own containment argument that the log is gitignored, local, and never leaves the machine. — risk: credential material printed by a failing-auth CLI propagates from the gitignored log into the durably-recorded quarantine reason and from there into gate output - commands/multi-review.md:479-480 — 🟡 low — On a round-N re-dispatch the log is truncated only when the background subshell's redirect opens, so "read the log before the first wait" can race it and read the PREVIOUS round's
dispatch exited <rc>line, quarantining a just-launched reviewer instantly. — risk: a healthy round-2 reviewer is quarantined at t=0 on round-1's stale crash line - commands/multi-review.md:489-491 — 🟡 low — The rc-zero row labels every unflipped copy "ran to completion and wrote nothing", but rc 0 + unflipped + copy-changed-since-seed (findings written, flip forgotten) is a distinct state the table gives the wrong name — and the wait's
--seedexists precisely to tell those apart. — risk: a copy carrying real findings is recorded asno turn taken, misinforming the gate
Multi-review
Agreed findings (17)
🟠 med — The PR summary still routes a status-present rc=0 copy through multi-review-wait, but the revised step-5 table quarantines that same unflipped byte-identical or changed copy immediately. — risk: the documented change has two incompatible outcomes for a cleanly exited unhanded-off reviewer
🟠 med — "The change" step-5 table is the stale round-1 design: it still keys off the exit code and contradicts the shipped bullets in commands/multi-review.md. — risk: the PR body teaches opposite semantics from the code it describes, in exactly the table round 3 was asked to scrutinize
🟠 med — The LAST-match rule only defuses echoed sentinels after the process dies: while it is still live, an echoed sentinel IS the last match and gets trusted. — risk: a live reviewer whose stdout echoes protocol text is judged exited — bullet 3 then admits a half-finished turn (or bullet 4 quarantines a live reviewer) while it is still writing
🟠 med — A shell reviewer that dies immediately after the pre-wait log read can still consume the first full wait bound before its non-zero sentinel is consulted. — risk: launch crashes can still be misclassified as slow for one bound
🟠 med — The fable-rd1-r4 fix relocates the stale-log race instead of closing it, yet the doc now claims it closed: rm -f runs INSIDE the background dispatch task, after the argv-building helper, so a pre-wait read can still see round N-1's status line — risk: a just-launched reviewer is quarantined on the previous round's dispatch exited <rc> — the exact bug r4 raised, now behind prose telling the primary to trust the log unconditionally
🟠 med — The rc-non-zero bullet quarantines immediately without consulting the seed, discarding a copy that CHANGED — on-disk findings the rc-zero bullet explicitly calls "recoverable rather than discardable" for the identical on-disk state — risk: a reviewer that wrote its findings and died one step before the flip loses its whole turn, the failure mode fable-rd1-r1 was agreed to prevent, protected here only when the death lands after the flip
🟠 med — rc-zero + changed copy defers to the exit-8 handler, whose premise — "demonstrably alive and still writing" — the log has just disproven: the primary spends up to 3 more 600s waits on a process it knows exited, then quarantines with the wrong reason — risk: the ~40-minute wait-on-a-corpse this PR exists to eliminate survives in the rc=0 lane, ending in still writing at the retry cap for a reviewer that stopped writing before the first retry; "recoverable" is never operationalized into an action
🟠 med — Ensure the appended exit sentinel starts on its own line; echo does not separate stderr that lacks a final newline. — risk: a crashed reviewer can still be classified as slow
🟠 med — The non-zero-rc row quarantines unconditionally, without first checking whether the marker flipped — a reviewer that completed its turn and then exited non-zero is discarded with its findings. — risk: a valid completed turn is quarantined as a crash and its findings never merge
🟠 med — The status line is appended with a bare echo onto whatever the CLI last wrote; a crashing CLI whose final stderr write lacks a trailing newline glues the two into one line, and the "last line is multi-review: dispatch exited <rc>" test then reads as Absent. — risk: the crash detection disarms in exactly the crash case it was built for — the dead reviewer reads as "still running" and burns the full retry budget again
🟠 med — The quarantine reason copies a verbatim log line (<the last non-empty line above that one>) into the coordination doc, which undercuts the Security section's own containment argument that the log is gitignored, local, and never leaves the machine. — risk: credential material printed by a failing-auth CLI propagates from the gitignored log into the durably-recorded quarantine reason and from there into gate output
🟡 low — Assertion (d) locates the seeding-step rm -f with a fresh fixed-offset window (sd..sd+20) in the same PR that twice documents fixed offsets shipping zero-margin (22, then 30, fable-rd2-r5). — risk: drift between 'snapshot each copy as' and the rm -f line past 20 rows breaks the check — loudly here, since it is a positive grep — but it re-seeds the drift class the rest of this file just converted to structural bounds
🟡 low — The status sentinel is spoofable by log content: the log is the CLI's verbatim stdout/stderr, "Look for the line multi-review: dispatch exited <rc>" names no occurrence rule, and in this repo's self-reviews the reviewed material itself contains that exact string (commands/multi-review.md:465,467,489 and any PR diff of them) — a CLI echoing prompt or doc content could plant a false status while still running — risk: a live reviewer is quarantined on a status line its own output quoted rather than the dispatch wrote
🟡 low — The widened n+30 window has zero margin again: the printf status line sits at exactly the window's last row (heading at commands/multi-review.md:437, printf at 467), reinstating the precise condition the widening comment warns about — the next line added above it pushes assertion (b) out of the window (loudly, but avoidably) — risk: the block breaks on the first unrelated insertion above the printf, and the comment's own rationale ("a window with no margin stops covering...") is contradicted by the chosen value
🟡 low — The new stale-log check reuses the label "(c)" already held by the log-consulted check three comments below, so the block reads (a) (b) (c) (c) (d) (e) — risk: cross-references to these subchecks (mutation-table comments, future findings) become ambiguous
🟡 low — On a round-N re-dispatch the log is truncated only when the background subshell's redirect opens, so "read the log before the first wait" can race it and read the PREVIOUS round's dispatch exited <rc> line, quarantining a just-launched reviewer instantly. — risk: a healthy round-2 reviewer is quarantined at t=0 on round-1's stale crash line
🟡 low — The rc-zero row labels every unflipped copy "ran to completion and wrote nothing", but rc 0 + unflipped + copy-changed-since-seed (findings written, flip forgotten) is a distinct state the table gives the wrong name — and the wait's --seed exists precisely to tell those apart. — risk: a copy carrying real findings is recorded as no turn taken, misinforming the gate
Disagreements (1)
🟡 low — Bullet 3 directs step-6 channel-check at a copy whose marker still says awaiting-reviewer; if channel-check or the merge step requires the flipped marker, the recovery path fails exactly when it is needed. — risk: the changed-then-died recovery is unrunnable in practice and degrades to the quarantine it was written to avoid — flagged by claude-fable-5; claude-opus-5 disputes: verified against both scripts: channel-check and merge each accept a copy whose marker was never flipped (exit 0, finding ingested), so the recovery path is runnable
———
🤖 Posted by AI agents (claude-opus-5 + gpt-5 + claude-fable-5) via multi-review star review.
| readable, and only otherwise quarantine, with `died mid-turn after writing; see | ||
| <doc>.<id>.multi-review.log`. Partial findings are worth more than a discarded round. Note |
There was a problem hiding this comment.
🟠 med — The PR summary still routes a status-present rc=0 copy through multi-review-wait, but the revised step-5 table quarantines that same unflipped byte-identical or changed copy immediately. — risk: the documented change has two incompatible outcomes for a cleanly exited unhanded-off reviewer — 🤖 multi-review star review (gpt-5 + claude-opus-5)
| this round's. The dispatch appends `multi-review: dispatch exited <rc>` on a line of its own once | ||
| the process is gone. **It counts only when it is the log's FINAL non-empty line.** A match | ||
| anywhere earlier is echoed text, not a status: the log is verbatim CLI output, and in this | ||
| repo's self-reviews the reviewed material contains the sentinel string verbatim. Taking merely |
There was a problem hiding this comment.
🟠 med — The LAST-match rule only defuses echoed sentinels after the process dies: while it is still live, an echoed sentinel IS the last match and gets trusted. — risk: a live reviewer whose stdout echoes protocol text is judged exited — bullet 3 then admits a half-finished turn (or bullet 4 quarantines a live reviewer) while it is still writing — 🤖 multi-review star review (claude-fable-5 + claude-opus-5)
| fi | ||
| fi | ||
|
|
||
| # (d) ...it lives in the SEEDING step instead, which the primary runs synchronously before any | ||
| # reviewer is dispatched, so nothing can race it. | ||
| sd="$(grep -n 'snapshot each copy as' "$DR" | head -1 | cut -d: -f1)" | ||
| # Bounded by the step that follows it, not an offset. A fixed window here would re-seed the | ||
| # exact class this file just converted away from — flagged as fable-rd3-r3. | ||
| sde="$(awk -v s="$sd" 'NR>s && /Then prove the copy is BLIND/ {print NR; exit}' "$DR")" | ||
| if [[ -z "$sd" || -z "$sde" ]]; then |
There was a problem hiding this comment.
🟡 low — Assertion (d) locates the seeding-step rm -f with a fresh fixed-offset window (sd..sd+20) in the same PR that twice documents fixed offsets shipping zero-margin (22, then 30, fable-rd2-r5). — risk: drift between 'snapshot each copy as' and the rm -f line past 20 rows breaks the check — loudly here, since it is a positive grep — but it re-seeds the drift class the rest of this file just converted to structural bounds — 🤖 multi-review star review (claude-fable-5 + claude-opus-5)
TL;DR: When a
geminireviewer crashes on launch, the review currently can't tell that apart from a reviewer that's just being slow — so it waits up to ~40 minutes and then blames the reviewer for "not taking a turn". This PR saves what the reviewer process actually printed, plus whether it died, and reads that before deciding. A crash now gets reported as a crash, immediately, with the real reason.The bug (G3)
The
shelldispatch branch consulted neither exit code nor stderr. A gemini that died on launch — bad flag, expired auth, exhausted quota — leaves a working copy byte-identical to its seed. So does a reviewer that is still thinking. Those are opposite facts and the protocol saw one value.What followed was all wrong, in order:
no turn taken, which is the reason for a reviewer that read the document and declined.The cause was on the process's own stderr the whole time. Nothing ever looked at it.
This is the same class as #66 and the
DISPATCH-FAILEDfix ina4bcf71: the round reports the symptom it can observe rather than the cause it never captured.The change
Dispatch captures both streams and its own exit status:
Step 5 reads that log before the first wait and again at every bound hit. The decision is keyed off the copy, not the exit code — the status answers only "has the process exited?", and it counts only as the log's final non-empty line (an earlier match is echoed text, and while the reviewer is alive the real status does not exist yet):
awaiting-authorchannel-check; quarantine only if the findings are unreadabledispatch exited <rc>; see <log>Residual, stated rather than implied: a process that dies just after the pre-wait read is not noticed until the next bound hit — one bound, against the four this removes.
.multi-review.logis an already-gitignored shape (.gitignore:1), so the new artifact does not leak into consuming repos and does not worsen the retained-artifact problem.Review history (this is what round 3 should look at)
Reviewed through this repo's own multi-review: codex (
gpt-5) + fable. Round 1: 6 findings. Round 2: 7 findings, all agreed, none disputed. Round 3 was requested explicitly by the engineer.Round 1 found that the status sentinel was unreadable in exactly the crash it targets — a bare
echoappends onto whatever the process last wrote, so a death mid-write producedquota exceededmulti-review: dispatch exited 1with no line-anchored match. Both vendors found this independently. Also: a flipped marker had to outrank a non-zero status (else a turn that completed then died on teardown was discarded with its findings); the quarantine reason must name the log rather than quote it (an auth error there carries a credential into a durably-recorded field); the previous round's log must be cleared; and the rc-zero branch mislabelled "flip forgotten" as "wrote nothing".Round 2 found that three of those round-1 fixes were wrong:
rm -fsat inside the dispatch block, which runs as a background task, so it raced the pre-wait read it existed to prevent. It now runs in the seeding step, synchronously.channel-check; byte-identical → quarantine. The exit code is evidence the process ended, nothing more.Plus the sentinel search now takes the LAST match (the log is verbatim CLI output, and this repo's self-reviews contain the sentinel string), and the residual cost is stated rather than implied: a process dying just after the pre-wait read is not noticed until the next bound hit — one bound, against the four this removes.
Two coverage failures the reviewers did not catch — the mutation sweep did:
--verify-tablestayed green. Only the full sweep caught it, as threeSURVIVEDand oneMISCREDITED.That is four instances in one PR of a verification that reads correctly and cannot fail. One was caught by the author, one by two reviewers, two by the mutation sweep.
Not addressed
The codex arm still discards its
task-<id>and quarantines blind (X1/X2 in the backlog). This PR fixes the shell arm only; the codex arm needs the companion's job registry, which is a dependency on its on-disk layout and a separate decision.Security
No new external input is parsed and nothing is transmitted. The log captures the reviewer CLI's stdout and stderr verbatim, and a CLI that fails to authenticate could in principle print credential material into it. The file is gitignored and local, never uploaded, and never read by any script. Round 1 closed the one path that would have moved that content somewhere durable (fable-rd1-r3): the quarantine reason now names the log rather than quoting it.
Verification
shellcheck --severity=warningclean onscripts/multi-review-*.shand.githooks/pre-push.multi-review-version-check.sh:1.23.1 → 1.24.0(minor — new behavior).--verify-table: 94/94 entries match.SURVIVED/MISCREDITED/STALE/ERROR, tree clean afterwards. An earlier sweep on this branch reported oneSURVIVED; that is the coverage gap described above, now fixed and re-verified.Not verified live: the
geminiCLI is not installed on this machine, so the shell arm is code-read only. No end-to-end run of a crashing gemini was performed. The sentinel behaviour was verified directly against a simulated dying process.🤖 Generated with Claude Code
https://claude.ai/code/session_01Nm3hNXA4WHbgKjFbxX7VUE