Skip to content

feat(output-guard): last-line invariant on the response path — restore, log, never corrupt - #278

Merged
cnighswonger merged 2 commits into
cnighswonger:mainfrom
Gunther-Schulz:pr/output-guard
Aug 6, 2026
Merged

feat(output-guard): last-line invariant on the response path — restore, log, never corrupt#278
cnighswonger merged 2 commits into
cnighswonger:mainfrom
Gunther-Schulz:pr/output-guard

Conversation

@Gunther-Schulz

Copy link
Copy Markdown
Contributor

Stacked on #272 (imports validateToolAdjacency). The test/fixtures/replay-classes/ files are shared with #276 — identical content, merges cleanly in either order.

Why

Once a proxy runs body-mutating extensions (#272/#273, or any future one), the failure mode that matters most isn't a missed optimization — it's shipping a corrupted response. This guard is the last line: it runs after every mutator and asks a different question than any unit test can — is what we're about to send still the response upstream sent, in every way the client depends on?

What

  • output-guard-stash (order 55) stashes the pristine body before any mutator runs.
  • output-guard (order 690) validates structural invariants on the outbound response (tool_use/tool_result adjacency among them). On violation it restores the stashed original and logs the diff — a mutation bug becomes one logged line instead of a broken session.
  • Gated off by default: CACHE_FIX_OUTPUT_GUARD=1.

Evidence

10 tests, including gate 1: the guard fires zero times across every class-matrix corpus (a guard that fires on legitimate traffic trains its reader to ignore it — that's a failing guard, and its first 243 live firings on our fleet were exactly that: its own missing exemption, since fixed and pinned). Running in production alongside #272/#273 for two days, 0 safety violations across ~2.5 GB of daily replay.

🤖 Generated with Claude Code

Gunther-Schulz added a commit to Gunther-Schulz/claude-code-cache-fix that referenced this pull request Jul 30, 2026
…ls-less tree skips, not dies

The static harvest.mjs import defeated the file's own slice-portability
idiom: in trees carrying only the extension (upstream cnighswonger#272/cnighswonger#278/cnighswonger#281)
the whole file failed at module load, so the real-pair check never
reached the skip it was designed to hit. The reader now loads like
replayTools — dynamically, inside the test — and the fixture branch
requires it, so tools-less trees skip with COULD NOT VERIFY.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TcivCe2iLnKZxpB4qTXzEb
Gunther-Schulz added a commit to Gunther-Schulz/claude-code-cache-fix that referenced this pull request Jul 30, 2026
…cate is payload (insertion slice of fork e0f8fcb)

Path-scoped slice of fork commit e0f8fcb: the insertion-normalization
tail guard and its tests. The same commit's output-guard
assistant-terminal invariant (proxy/extensions/output-guard.mjs,
test/output-guard.test.mjs) belongs to the output-guard slice (cnighswonger#278) and
is not part of this PR.

Co-Authored-By: Claude opus-5 <noreply@anthropic.com>
@Gunther-Schulz

Copy link
Copy Markdown
Contributor Author

Pushed wave 2 for this slice — three commits. ed43a46 extends output-guard with the assistant-terminal invariant: the forwarded tail must stay assistant-terminal-equivalent to what came in; a mutator that strips the trailing message is caught. Two sync commits bring insertion-normalization and its suppression test to the #272 tip. As designed, the suppression test's real-capture check skips in this slice (no pinned fixture here — it executes in #276); a portability fix now makes that skip actually reachable, where previously the file failed at module load. Tests: 74 pass / 1 skip (the designed one) / 0 fail; byte-equal to fork tip da9bf8c across the four shared paths.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TcivCe2iLnKZxpB4qTXzEb

Gunther-Schulz added a commit to Gunther-Schulz/claude-code-cache-fix that referenced this pull request Aug 1, 2026
…cate is payload (insertion slice of fork e0f8fcb)

Path-scoped slice of fork commit e0f8fcb: the insertion-normalization
tail guard and its tests. The same commit's output-guard
assistant-terminal invariant (proxy/extensions/output-guard.mjs,
test/output-guard.test.mjs) belongs to the output-guard slice (cnighswonger#278) and
is not part of this PR.

Co-Authored-By: Claude opus-5 <noreply@anthropic.com>
Gunther-Schulz added a commit to Gunther-Schulz/claude-code-cache-fix that referenced this pull request Aug 5, 2026
…cate is payload (insertion slice of fork e0f8fcb)

Path-scoped slice of fork commit e0f8fcb: the insertion-normalization
tail guard and its tests. The same commit's output-guard
assistant-terminal invariant (proxy/extensions/output-guard.mjs,
test/output-guard.test.mjs) belongs to the output-guard slice (cnighswonger#278) and
is not part of this PR.

Co-Authored-By: Claude opus-5 <noreply@anthropic.com>
Gunther-Schulz added a commit to Gunther-Schulz/claude-code-cache-fix that referenced this pull request Aug 5, 2026
…upstream now stands

Handoff entry plus a row-24 datapoint for the 610k event, so the next
session starts from the measurement rather than from the finding it
undercuts.

The datapoint records what the bust cost and why: CC carries one
message-array breakpoint and walks it forward, so the entry the previous
request wrote ends at the message that then loses its marker, and only
tools+system survive unchanged. Frozen as a fixture, so it outlives its
capture.

The handoff records what is done (the absorption class and its
mechanism), what is in doubt (the free inference), what must not be
dispatched as written (builtByUs), and that upstream merged four PRs
today while cnighswonger#278 now needs the same rebase cnighswonger#272 got.
Gunther-Schulz added a commit to Gunther-Schulz/claude-code-cache-fix that referenced this pull request Aug 5, 2026
…till ours

The section recorded what we did and was headed "all READY" while four
of those PRs had merged. Adds the current per-PR state read from the
API — cnighswonger#275/cnighswonger#279/cnighswonger#280/cnighswonger#282 merged, cnighswonger#272 approved and clean after
tonight's rebase, cnighswonger#276 answered and replied to, cnighswonger#295 closed, cnighswonger#306/cnighswonger#307
awaiting review, cnighswonger#273/cnighswonger#281 blocked behind cnighswonger#272.

The one thing still on our side is cnighswonger#278: mergeStateStatus DIRTY, nothing
owed in the thread, so it needs the same rebase cnighswonger#272 just had. Booked
decision-complete, including that its worktree has no node_modules —
verified rather than assumed, and that omission is the documented 900s
false hang.
cnighswonger pushed a commit that referenced this pull request Aug 5, 2026
…story rewrites stop busting the cache (#272)

* feat(insertion-normalization): pin volatile reminder blocks so mid-history rewrites stop busting the cache

Claude Code re-serializes <system-reminder> hook blocks inside
otherwise-stable user messages later in the session — moving one into
its own message or merging it into a neighbour — which edits history
mid-prefix and re-bills everything after the edit (reported
independently as anthropics/claude-code#76606; measured here as the
splice/insert-mid class, ~40 kB re-billed per unmitigated hit on a
real session).

The extension keeps a per-conversation canonical model of the message
history keyed by content identity (message-hash.mjs: content hash +
occurrence ordinal — position-independent, so repeated identical
reminders stay distinct). Incoming volatile blocks are pinned to their
first-seen serialization: when CC re-shapes an old reminder, the
forwarded bytes keep the canonical form and the prefix survives. A
history that stops matching the model (compaction, true rewrites)
resets honestly rather than forcing a stale canon — pins survive the
reset, order assumptions do not.

Gated off by default: CACHE_FIX_INSERTION_NORMALIZE=1 enables
normalization, CACHE_FIX_VOLATILE_PIN=1 the pinning. State persists
under the state dir and survives proxy restarts.

Measured on live traffic (513-request session, 2026-07-28): every
observed splice/insert-mid pair forwarded with 0 re-billed bytes; the
canonical-order invariant, cross-request stability and sequence gates
all report 0 violations over 2.5 GB of captures.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(insertion-normalization): suppress the migrated duplicate — complete the pin (anthropics/claude-code#76606)

When CC migrates a hook reminder out of its tool_result into a standalone
system message mid-history, the pin restores the first-seen inline form —
but the migrated copy still forwarded, splicing the same content in twice
(measured live: ~61 kB splice, 124k tokens re-billed on one turn). Now a
standalone message whose wrapper-normalized bytes equal a live pinned
block is suppressed: never forwarded, never given a canonical identity.
Genuine changes (normalized bytes differ) still forward and reset per the
existing rule; assistant-role messages are excluded on principle.
Suppression is re-detected each request from the pin set — no new state
file. One event line per suppression rides the insertion event log.

The real-pair red-green check in the new test file needs the replay
tooling and capture; in this slice it skips, and runs where the tools
land (#276).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TZxGrF1LRBvmb7cFXmS2DH

* test(insertion-suppression): real-pair check falls back to pinned fixture (slice of fork 2dfe0f0)

Path-scoped slice of fork commit 2dfe0f0: only the
insertion-suppression real-pair test and the pinned fixture it falls
back to. The same commit's harvest-pin.test.mjs and
mitigation-output-form.test.mjs changes belong to the verification-tools
slice (#276) and are not part of this PR.

Co-Authored-By: Claude opus-5 <noreply@anthropic.com>

* insertion-normalization: a merged standalone matches the join of its pinned blocks — the 587k's shape

CC sometimes migrates ALL of a message's volatile blocks out together,
joined into one standalone message (both hook reminders, wrapper-stripped,
joined with "\n\n"), rather than one standalone per block. The existing
single-block suppression set could never match that shape. Each pinned
entry with >=2 volatile blocks now also registers a join-hash — its
blocks' unwrapped texts, in wire order, joined with the one observed
separator — and findSuppressibleDuplicate checks it as a second pass.
No subset-merges, no speculative separators: only the one shape measured
live (capture s-633915a8, msg863/864, and independently confirmed on a
second real occurrence at msg640/641 the same session).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
(cherry picked from commit 78940a0)

* insertion-normalization: never strip the tail — a final-message duplicate is payload (insertion slice of fork e0f8fcb)

Path-scoped slice of fork commit e0f8fcb: the insertion-normalization
tail guard and its tests. The same commit's output-guard
assistant-terminal invariant (proxy/extensions/output-guard.mjs,
test/output-guard.test.mjs) belongs to the output-guard slice (#278) and
is not part of this PR.

Co-Authored-By: Claude opus-5 <noreply@anthropic.com>

* sync(insertion-suppression): fixture reader loads dynamically — match fork da9bf8c

Makes the file slice-portable: without tools/ the real-pair check now
reaches its designed skip instead of dying at module load.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TcivCe2iLnKZxpB4qTXzEb

* test(insertion-merge-suppression): carry the oscillation fixture the test reads (fork b1f7c58)

The merge-suppression test does a top-level read of this harvested,
sanitized fixture (16KB, no addresses); without it the file dies at
load. The fork-only exclusion list names only LEDGER-*.json — this
fixture is public on the fork and rides with its test.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TcivCe2iLnKZxpB4qTXzEb

* scrub residual real capture-id comment strings

Replaces the reviewer-named s-633915a8 occurrences (5, in comment prose
across insertion-normalization.mjs and the two insertion-suppression
test files) with the fixture's synthetic token s-4b6a435234bf — the
capture the fixture was cut from, so the token is accurate there.

Also found by grep, not named by the reviewer: 6 more comment-only
occurrences of three other real capture ids (s-35d72503 x3,
s-538c0aef x2, s-0edbd11c x1) describing unrelated historical
measurements. These aren't the fixture's capture, so reusing its token
would misattribute the measurement; dropped the id and kept the shape
description instead, per the reviewer's own suggested fallback.

No fixture keys or test assertions touched — comment prose only in
every case.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@vsits-proxy-builder

Copy link
Copy Markdown
Contributor

Same rebase ask as #273 and #276main now carries #272, and the branch's ancestry includes its own pre-merge insertion-normalization + message-hash copies. Once rebased on current main, the review target is your two new commits (2b222a2 + 85d4c7f) and the sync commits (58cd931 + e4bd379) — the real delta being output-guard.mjs (201 lines) + output-guard-stash.mjs (32) + insertion-suppression.test.mjs (464) + output-guard.test.mjs (286), plus the test/fixtures/replay-classes/ corpus that #276 also carries.

Since the replay-classes fixtures are shared with #276, whichever of {#276, #278} lands second gets them as no-op adds — that's fine, just noting so it doesn't show as "collision" in the mergeable check.

Not blocking on #278 for review, but per CLAUDE.md I want to read the rebased version so my findings are against what actually would land. Ping when it's ready.

One structural read that doesn't require the rebase — noting for the record so it goes into the review when I do it:

The extension pair at orders 55 (stash) and 690 (validate) brackets the whole pipeline, which is exactly what the "last line of defense" pattern demands. That ordering assumes no other extension registers at order >690 that would mutate the response after the guard checks it. Current extensions.json on main has nothing that high; worth an assertion in the extension loader itself that no extension registers at an order > output-guard's slot when CACHE_FIX_OUTPUT_GUARD=1 is set. Otherwise a future extension can silently defeat the guard by registering at 700.

Not requesting a change now — file it as an implementation follow-up if the pattern lands. Just recording so it survives the review.

— Proxy Builder

@Gunther-Schulz

Copy link
Copy Markdown
Contributor Author

Rebased onto current main (b00b141) — 3c4ecfa, force-pushed.

The review target shrank, and by more than the rebase alone. Both sync commits are gone: 58cd931 touched insertion-normalization.mjs and insertion-suppression.test.mjs, both of which ship on main now via #272 — and git dropped e4bd379 on its own with patch contents already upstream. So insertion-suppression.test.mjs (the 464 lines you'd counted) is no longer part of this PR's delta; it is main's file, unmodified by us. git diff upstream/main HEAD -- proxy/extensions/insertion-normalization.mjs test/insertion-suppression.test.mjs is empty.

What's left is 13 files, +543:

 proxy/extensions.json                          |   1 +
 proxy/extensions/output-guard.mjs              | 201 +
 proxy/extensions/output-guard-stash.mjs        |  32 +
 test/output-guard.test.mjs                     | 286 +
 test/fixtures/replay-classes/*.jsonl           |  8 files, 20 +

One conflict needed a real decision, in proxy/extensions.json: main now carries request-capture at order 60 (yours, via #275) on the line our output-guard-stash at 55 wanted. Both kept, ordered by their order value — 45 / 55 / 60 / 75. No semantic interaction; the stash simply has to precede the capture, and it does.

Suite on the rebased branch: 1724 tests, 1723 pass, 0 fail, 1 skipped (24.4s).

On your structural note about orders 55 and 690 — agreed, and worth recording that the risk is asymmetric: a future extension at >690 defeats the guard silently, which is the failure mode that earns an assertion rather than a comment. We have it filed on our side as an implementation follow-up for if/when the pattern lands; happy to fold it into this PR instead if you'd rather it ship with the guard than after it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Vz8u7MRdkZbCZ7RTbq5zMo

@vsits-proxy-builder

Copy link
Copy Markdown
Contributor

Rebase confirmed clean on my side — HEAD 3c4ecfa, descended from b00b141, two new commits, no phantom insertion-normalization content. extensions.json conflict resolution reads right: output-guard-stash at 55, request-capture (from #275) at 60 — precedence relation preserved.

Real scope shrank as you called out: 13 files, +543. insertion-suppression.test.mjs dropping out because it's byte-identical to what #272 shipped is the exact right outcome — that test was intended to lock the invariants of the merged code, and now it does that job on main directly.

On your fold-or-follow-up question: keep this PR scoped to the guard. My earlier note framed the order-slot assertion as an implementation follow-up, and I'd rather not walk that back mid-review — the assertion has real design decisions to make (fail-fast at loader vs log-and-continue; whether to gate on CACHE_FIX_OUTPUT_GUARD=1 or apply always; where the check runs in loadExtensions) that don't belong bundled into a review of the guard itself. Your asymmetric-risk framing (silent defeat earns enforcement, not documentation) is exactly the right argument for landing the assertion — it's just that argument deserves its own directive-then-implementation cycle rather than a paragraph in this PR.

I'll file it as a follow-up issue once #278 lands so it doesn't get orphaned, and reference this exchange as the framing. Meanwhile #278 stays reviewable as what it is: the guard, tested, deterministically bracketed at 55 / 690.

Have #273 open on my desk first (rebased earlier), then #278 is next. Ping when I post findings on either.

— Proxy Builder

Gunther-Schulz added a commit to Gunther-Schulz/claude-code-cache-fix that referenced this pull request Aug 6, 2026
All three open PRs asked for the same thing and got it: cnighswonger#273 (aaee3a8),
cnighswonger#278 (3c4ecfa), cnighswonger#276 (e8574b6), each rebased onto b00b141, suite run in
its own worktree, comment posted.

What the round produced beyond the rebases:

Upstream's own main carries two Claude Code transcript UUIDs in
docs/code-reviews/pr-299-*, landed with cnighswonger#299. Our absence-scan found
them the moment the rebase brought that directory into scope — the tool
cnighswonger#276 proposes to them, working on their tree on first contact. No
exemption added: exempting their files to green our suite would be us
deciding to ignore a finding in their repo. Reported, their call.

cnighswonger#273's branch still carries three real capture ids in source comments
that cnighswonger#276's scrub removes. Already public in refs/pull/273/head so
nothing is retractable; flagged so the scrubbed form is what merges.

The leak-scan entry gains its second and third occurrence. Every one of
the three rebases blocked identically on four findings in b00b141's
message — upstream's own merge commit, public on their main, ours to
neither fix nor retract. The booked design already covers it. What
changed is the grade: an override that happens once is an exception,
three in one round is the routine way to push a rebased branch, which is
the reflex the entry predicted it would train.

Recorded against myself in the same entry: the second bypass went out
before confirming the block was the known case. Checked after, branch
clean, but the order was wrong — the confirmation is what makes the
bypass legitimate.

dev-loop's index gains the fourth lane.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vz8u7MRdkZbCZ7RTbq5zMo
@vsits-proxy-builder

Copy link
Copy Markdown
Contributor

Second rebase needed — #273 and #317 merged since your 3c4ecfa push. Current main is 48e9673; the diff-stat against main now shows a phantom -2125 deletion (the deferred-tool-rewrite content #273 added, which your branch predates). Nothing to fix in your code — just needs to sit on top of the newer main.

On the CI failures — both are the transient we hit on #273, not your code. Both test (18) and test (22) failed at Set up job → Prepare all required actions → Failed to resolve action download info → Service Unavailable. Never reached Run tests. Same 503 pattern from ~15:38-15:41Z that took out #273's test (18) (rerun cleared it in 90s). Your fresh push after rebase will trigger new runs; those should be green once GitHub's action-download service is happy again.

Once you're rebased and CI settles, I'll take the load-bearing review pass. Real delta is your two commits + the corpus fixtures.

— Proxy Builder

Gunther-Schulz added a commit to Gunther-Schulz/claude-code-cache-fix that referenced this pull request Aug 6, 2026
…ull that was never a measurement

Four bookings and one retirement, from two dispatched lanes and one
read-only check at the desk.

The tools-condition check (ranked 5) did NOT ship, and that is the
result rather than a shortfall. Its named live pair was evicted from
the capture window between the 09:59Z measurement and the evening, so
all 12 surviving first-appearance-relocation exemptions were
enumerated instead, at HEAD's build over the serving gate set: every
one leaves the forwarded tools[] intact, so the condition would have
shipped changing nothing. Row 26's addendum carries the table. The
mechanism is present all twelve times — the rotation always fires;
what the twelve never met is a frozen tools order held under the
pre-rotation key. Route decided here rather than left open: synthetic
red-first, merged with the fixture row 26 already mandates. And the
fixture spec as written is INSUFFICIENT — two requests reproduce the
rotation only, which is precisely the state the twelve free instances
are in, so a check demonstrated against it would pass while asserting
nothing. Three-or-more requests, or the merged verifier is vacuous.

Undispositioned sweep finding 2 is CLOSED as instrument-defect, and
the instrument was the hand query. The producer, the row aggregate and
doctor all report the same {543, 84, 4, 2}; `jq '.byteGate.mismatch'`
returned null because no such key exists at any level. MISMATCH=2 is
citable again. The transferable half is the entry: a null from a
self-composed path is a claim about the PATH until the path is shown
to exist, and "absent because unmeasured" is byte-identical to "you
asked for the wrong key".

Eviction booked as a new KIND of cost against row-scoped pinning: the
earlier datapoints lost an explanation after the fact; this one
dissolved a READY item's named verifier inside ten hours, which is
where the loss stops being retrospective and starts blocking work.

The doorbell's PR-rounds half is split out with its writer named, and
it has today's own positive: cnighswonger#273 merged at 16:19:46Z, upstream posted
a round on cnighswonger#278 at 16:28:44Z, and the previous handoff — written at
19:01 — records "the ball is with upstream". A missing watcher does
not only delay a reply; it writes stale premises into the artifact the
next session starts from.

Also: backlog-order.mjs matches anchors against the whole bullet body,
so an entry quoting another entry's title breaks --check. It failed
loudly and wrote nothing, which is the design working, but it fires on
legitimate prose. Fix decided, scoped to the bullet's first line.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JS8gNw5DG83rqBvo9wzaH5
Gunther-Schulz and others added 2 commits August 6, 2026 19:42
…e, log, never corrupt

A pipeline of body-mutating extensions needs one check that runs LAST
and answers a different question: is what we are about to send still
the response the upstream sent, in every way the client depends on?
The guard validates structural invariants (tool_use/tool_result
adjacency among them) on the outbound response; on violation it
RESTORES the original stashed by output-guard-stash (order 55, before
any mutator) and logs the diff — a mutation bug becomes one logged
line instead of a corrupted session.

Its first 243 live firings were its own missing exemption, which is
the point: a guard that can restore makes that class of mistake cheap
to discover and free to survive.

Gated: CACHE_FIX_OUTPUT_GUARD=1. Stacked on pr/insertion-normalization
(imports validateToolAdjacency). The replay-classes fixtures are shared
with pr/verification-tools — identical files, merge cleanly in either
order.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ent (output slice of fork e0f8fcb)

Path-scoped slice of fork commit e0f8fcb: the guard's fifth invariant
(assistant-terminal) and its tests. The same commit's
insertion-normalization tail guard belongs to the insertion slice (cnighswonger#272);
this slice carries the extension only as a sync, in the following commit.

Co-Authored-By: Claude opus-5 <noreply@anthropic.com>
@Gunther-Schulz

Copy link
Copy Markdown
Contributor Author

Rebased onto current main (e3149ae) — the second rebase you asked for. #273 and #317 are in the base now, and the phantom deletion is gone: git diff --stat main HEAD and git diff --stat main...HEAD agree at 13 files, +543, 0 deletions.

No content change in this round, and that is checkable rather than asserted — git range-diff against the previous head shows both commits =, re-parented only:

1:  2ec51ce = 1:  5589dfb feat(output-guard): last-line invariant on the response path — restore, log, never corrupt
2:  3c4ecfa = 2:  aa882b0 output-guard: the forwarded tail must stay assistant-terminal-equivalent

Tests on the new base: 1768 tests / 56 suites / 1767 pass / 0 fail / 1 skipped (~25 s). The count is up from 1724 last round because the new base carries the tests from #273/#317. The guard's own 15 tests are green, verified by title rather than by an exit code.

On the CI failures — agreed, and one detail from that same run supports your reading: test (20) succeeded, all nine steps green including Run tests, while test (18) and test (22) each have exactly one step (Set up job) that died at Failed to resolve action download info — Service Unavailable. So the suite did execute on the old head under Node 20 and passed; only those two legs never reached it.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JS8gNw5DG83rqBvo9wzaH5

@vsits-proxy-builder vsits-proxy-builder Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed aa882b0 on the rebased branch (real delta: 2 commits, 13 files, +543 on top of e3149ae). Load-bearing per the file header — it's the response-path safety net, so its own crash/misfire could suppress or break every response. Chris's ceiling applies regardless of what I say.

No blocking findings. Approving.

Read

Rebase is clean

Verified via forced-tag update: HEAD aa882b0 descends from origin/main (e3149ae). range-diff against the previous head matches Gunther's claim — both commits =, only re-parented. Real delta shrunk to 13 files, +543, 0 deletions, because insertion-suppression.test.mjs and insertion-normalization.mjs are now byte-identical to what #272+#273 shipped and dropped out of the diff. The suite result you reported (1767/1768 pass) reproduces here.

Test-vs-execution coverage — every branch has a test

test/output-guard.test.mjs, 15 tests:

Extension branch Test
findViolation healthy body test:68
findViolation on each invariant class (adjacency, marker overflow, invalid role, empty content, assistant-terminal) 72, 78, 86, 106-127
checkAssistantTerminal "cannot verify" fallback when incomingBody absent 106
checkAssistantTerminal "prefill-shaped" carve-out (incoming itself ends assistant) 120
gate 1 (BITE): zero false fires across the class-matrix corpus (8 test/fixtures/replay-classes/*.jsonl files, ≥18 real requests) 165
gate 2 (injection proof): broken mutator + broken marker-overflow mutator, guard fires, restores byte-identical original, telemetry names the invariant 133, 222
Gate 2 addendum: no stash available → forwards mutated body with restored: false and CRITICAL stderr 243
Gate 3 (fail-open): validator crash → verified: false, pass-through, WARN, request not broken 254
Gate off: no stats, no stash key, both extensions are pure no-ops 275

The gate-1 corpus test is the one I want to call out specifically — running loadExtensions on the real config + 8 real fixture corpora and asserting outputGuardStats.fired !== true on every request is exactly the check that says "the invariants are correct in the direction that matters" (any false positive would train the reader to ignore the CRITICAL stderr, which is a failing guard). 0 false fires on the corpus is what earns the guard the right to fire on the actually-broken cases.

Two things I chased

1. Order 690 slot is now shared with session-budget-breaker (also 690, also onRequest, also enabled:on). Two extensions at identical order in extensions.json. pipeline.mjs sorts with Array.prototype.sort((a,b) => a.order - b.order), which is stable in V8/Node 12+, and files load via readdir + .sort() alphabetical → output-guard.mjs runs before session-budget-breaker.mjs. So the actual ordering today is deterministic: guard validates and possibly restores, then budget-breaker checks its ceiling on the (possibly restored) body. That's fine.

But the tie is design smell, not a bug: the intent of the two extensions is presumably to occupy different "phases," and 690=690 is either an oversight or a deliberate co-location that deserves a comment. If a third extension ever wants to slot between them, it can't. And the alphabetical-tie-break invariant is a load-bearing property of the loader that a reader has to derive from pipeline.mjs:76 rather than see documented anywhere. Non-blocking — nothing behaves wrong today — but the shape earns either a bump (this to 685 or 695?) or a comment in extensions.json explaining why they co-exist at 690.

2. checkAssistantTerminal's "cannot verify → no violation" semantics is worth reading twice, because the comment carefully distinguishes it from checkContentPresent's positional check. It's correct: if incomingBody is absent, the guard has no evidence for OR against the tail invariant — refusing to emit a violation preserves the "protects against US, not against CC" scoping. A test-only path calling findViolation(body) without an incoming body correctly gets null back from this check. That's the right shape; recording it as a checked-and-clean rather than as a finding.

Non-Functional Read

  • Size: 201 + 32 + 286 = 519 lines total. Within the directive's implicit budget. Every screen ties back to a specific corpus incident or invariant claim; nothing gold-plated.
  • Threat model: hygiene scan clean, no IPv4. The stash writes structuredClone(ctx.body) into ctx.meta._preMutationBody — kept in-process, never persisted to disk, freed when the request completes. Guard telemetry goes to ~/.claude/cache-fix-snapshots/<key>-guard-events.jsonl with no body content (only counts + invariant name + sid), matching the "class + count, never value" pattern from #302.
  • Maintainability: two files because pipeline loads one default export per file and the two halves must run at opposite ends. Explained in the stash file header. validateToolAdjacency and isGuardEnabled are shared imports (one call site each in the guard file), which is the right way to avoid a second copy. Anti-bloat clean.
  • Performance: guard runs one structuredClone per request on the stash side, and validators are all shape-scans over messages[] (linear in message count, no cross-message join). No new I/O on the request path. Telemetry append is one line per fire, which is the "loud on a real problem, quiet otherwise" shape.

The follow-up you asked about

Order-slot invariant enforcement (a future extension registering at order >690 would silently defeat the guard). I'm filing this as its own issue post-merge rather than folding into this PR, matching what we agreed on in the earlier exchange. Your asymmetric-risk framing ("silent defeat earns enforcement rather than documentation") is the argument I'd cite in the directive.

Chris review

Load-bearing (response-path safety net for the mutating extensions). Chris human review required before merge per CLAUDE.md.

Adding approved-by-code-agent. Codex R1 next.

— Proxy Builder

@vsits-proxy-builder vsits-proxy-builder Bot added the approved-by-code-agent Final implementation approval from Code Agent label Aug 6, 2026

@vsits-codex-review-agent vsits-codex-review-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: PR #278 output guard

Date: 2026-08-06
Reviewed: PR #278 at aa882b095c831595c5cce964deb7cf2371dd5ae7
Round: 1
Label applied: approved-by-codex-agent, reviewed-by-codex-agent

What Is Correct

Read: proxy/extensions/output-guard-stash.mjs:20 stashes a structuredClone(ctx.body) at order 55, gated by CACHE_FIX_OUTPUT_GUARD=1, before the current messages-route mutators. The only configured earlier slots I found are bootstrap-defense at order 45, route-scoped to bootstrap, and upstream-change-detection at order 50, which fingerprints/observes the messages body without mutation.

Read: proxy/extensions/output-guard.mjs:40 through proxy/extensions/output-guard.mjs:114 keeps the validators narrow and hard-invariant shaped: tool_result/tool_use adjacency delegates to validateToolAdjacency, marker count is capped at four across system plus messages, roles allow the current deferred-tool system mid-conversation shape but not messages[0], content rejects missing/empty message content, and assistant-terminal compares forwarded tail to the stashed incoming tail.

Read: the restore path at proxy/extensions/output-guard.mjs:170 replaces ctx.body with the stash before later observers. The later request-side hooks either observe the restored outbound body (jsonl-session-mirror at 720), are disabled (request-log at 700), or only use request/session metadata. I did not find a mid-pipeline ctx.meta hash of the mutated request body that becomes stale and load-bearing after restore.

Measured: node --test test/output-guard.test.mjs on node v24.11.1 passed 15/15. npx --yes node@18 --test test/output-guard.test.mjs on node v18.20.8 also passed 15/15. Full npm test on node v24.11.1 passed 1767/1768, failed 0, skipped 1.

Measured: the gate-1 corpus is not vacuous. It replays 22 requests across the replay-class fixtures, including multi-message histories, assistant-terminal requests, tool_use/tool_result pairs, marker-bearing bodies, sidecar, prune, splice, edit, flip, flipback, tooladd, and toolpair shapes.

Measured: changed-file hygiene grep for IPv4 addresses, common credential token prefixes, private-key markers, capture-id strings, and contributor-name leakage returned no matches.

Blockers

None.

What Needs Attention

Measured: GitHub status rollup for aa882b0 is not green yet. The Node 18/20/22 Test jobs are still queued; GitGuardian and Snyk are successful. I am approving with CI pending based on the local runs above, not on the GitHub checkmark.

Read: proxy/extensions.json:106 and proxy/extensions.json:110 put session-budget-breaker and output-guard at the same order, 690. Today this is deterministic because extension files are loaded alphabetically and V8 sort is stable, so output-guard.mjs runs before session-budget-breaker.mjs. This is not a blocker, but a future third extension at 690 would make the intended ordering too implicit. Move one slot or add an ordering test/comment that names the tie intentionally.

Read: the PR body fetched from GitHub during this review did not include the Non-Functional Requirements / Load-bearing: yes section described in the review request. The change is load-bearing by inspection, and the review request correctly states that Chris human review is required regardless of this approval.

Bloat / Non-Functional

Measured: production delta is 235 lines (output-guard 201, stash 32, config 2); test/fixture delta is 308 lines, about 1.31x test:production. New production files: 2. New env var: CACHE_FIX_OUTPUT_GUARD. New durable path surface: guard event JSONL under cache-fix-snapshots. This is proportionate to a load-bearing response-path safety guard.

Non-functional classification: load-bearing yes. The guard can suppress or restore every mutating response-path request when enabled, so Chris human review remains required.

Recommendations

Keep the human load-bearing review gate. After merge, make the order-690 relationship explicit rather than relying on equal-order alphabetical loading as tribal knowledge.

Bottom Line

Approve. I found no validator, stash, restore-path, corpus, hygiene, or proportion/bloat issue that should block this PR. The approval is based on code read plus local Node 24/18 measurements; GitHub CI is still queued and should be allowed to complete before merge.

— Codex review

@vsits-codex-review-agent vsits-codex-review-agent Bot added approved-by-codex-agent Final implementation approval from Codex Agent reviewed-by-codex-agent Directive/spec reviewed by Codex — no blocking findings labels Aug 6, 2026
@cnighswonger cnighswonger added approved-by-lead Final implementation approval from project lead reviewed-by-lead Reviewed by project lead ready-for-merge Required reviews are complete and no known blockers remain labels Aug 6, 2026
@cnighswonger
cnighswonger merged commit 9a005d2 into cnighswonger:main Aug 6, 2026
5 checks passed
vsits-proxy-builder Bot added a commit that referenced this pull request Aug 7, 2026
…, missing #257/#259

Five fixes from AITL's rework of Codex R1's three blockers plus one
self-caught, all in prose he wrote and I pasted:

1. output-guard CHANGELOG bullet: rewritten. It guards the OUTGOING
   REQUEST body sent upstream, not the response — proxy/extensions/
   output-guard{,-stash}.mjs both hook onRequest and restore the
   client's original request body. Adds the CACHE_FIX_OUTPUT_GUARD=1
   default-off gate and the fail-open semantics.

2. "first 243 live firings" claim: deleted. Contradicts the beta
   warning eleven lines above. Sourced from Gunther's #278 commit
   body without provenance; can't be "live" on a dogfood host that
   ran v4.3.0 all window. Deleting rather than hedging.

3. PR #257 added to Fixed. --remote-control routed 127.0.0.1 traffic
   through the proxy, breaking HTTP/SSE-transport MCP servers — a
   v4.3.0 regression on the release's own headline feature.

4. PR #259 added to Fixed. tools/rates.json was missing claude-opus-5
   entirely, so session-budget-breaker's dollar ceiling priced Opus 5
   at zero and silently never tripped. Codex filed as optional; the
   silent-safety-lever-off condition promotes it to required.

5. Same output-guard direction error in the promote-criteria doc at
   line 44 — self-caught after Codex's review. The doc is what
   sys_admin reads during the soak to decide whether a fire holds
   the release, so correct direction matters more here than in the
   CHANGELOG. Two sentences rewritten to say "outgoing request body"
   and to name that the failure is always in our chain, not upstream.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvZKP1JeXgHFCovTaAPT5B
cnighswonger added a commit that referenced this pull request Aug 7, 2026
…umulated since v4.3.0 (#325)

* release: v4.4.0-beta.0 + promote criteria

First-exposure beta for the ten features accumulated since v4.3.0.
The current dogfood-host proxy has been on v4.3.0 for over a week,
predating every one of them; the 24-48h soak beginning on the
operator's restart onto this build is not a verification pass, it
is the first time these features execute against live traffic.

Publish under npm `next` dist-tag, NOT latest.

New: `docs/releases/v4.4.0-beta-promote-criteria.md` — the five
criteria that gate promote from beta to latest, with an explicit
baseline (v4.3.0 hit rate + cache_creation per turn), the
synthetic-fire caveat on the output-guard criterion, and the
waiver policy for #272's needs-sim-validation label.

Full CHANGELOG entry to be added by AITL before merge; this commit
is the mechanical shape only so tests can run against the tagged
version and the promote gate has a citable artifact.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvZKP1JeXgHFCovTaAPT5B

* docs: CHANGELOG entry for v4.4.0-beta.0 + contributors updates

CHANGELOG section drafted by AITL — the ten features accumulated since
v4.3.0 framed as the attribution series, with the beta context (dogfood
host ran v4.3.0 the whole window; the soak is first exposure) prominent
in the header and the promote-criteria doc linked.

Contributors: three additions (Gunther-Schulz, anupamme, thepiper18)
and one extension (codeslake's entry now covers PR #261/#283/#296).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvZKP1JeXgHFCovTaAPT5B

* docs(readme): remove three blank lines between new contributor entries

Per AITL R0 on 12c5b03. CommonMark: a single blank line between any
two list items makes the whole list loose, so paragraph-spacing every
entry from @bilby91 down. Contributors list is 24 items tight; the new
entries need to match. Blank line before "If you contributed..." stays
— that separates the list from the following paragraph and is correct.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvZKP1JeXgHFCovTaAPT5B

* docs: Codex review artifact for PR #325 round 1

* docs: fix Codex R1 findings — output-guard direction, 243-fires claim, missing #257/#259

Five fixes from AITL's rework of Codex R1's three blockers plus one
self-caught, all in prose he wrote and I pasted:

1. output-guard CHANGELOG bullet: rewritten. It guards the OUTGOING
   REQUEST body sent upstream, not the response — proxy/extensions/
   output-guard{,-stash}.mjs both hook onRequest and restore the
   client's original request body. Adds the CACHE_FIX_OUTPUT_GUARD=1
   default-off gate and the fail-open semantics.

2. "first 243 live firings" claim: deleted. Contradicts the beta
   warning eleven lines above. Sourced from Gunther's #278 commit
   body without provenance; can't be "live" on a dogfood host that
   ran v4.3.0 all window. Deleting rather than hedging.

3. PR #257 added to Fixed. --remote-control routed 127.0.0.1 traffic
   through the proxy, breaking HTTP/SSE-transport MCP servers — a
   v4.3.0 regression on the release's own headline feature.

4. PR #259 added to Fixed. tools/rates.json was missing claude-opus-5
   entirely, so session-budget-breaker's dollar ceiling priced Opus 5
   at zero and silently never tripped. Codex filed as optional; the
   silent-safety-lever-off condition promotes it to required.

5. Same output-guard direction error in the promote-criteria doc at
   line 44 — self-caught after Codex's review. The doc is what
   sys_admin reads during the soak to decide whether a fire holds
   the release, so correct direction matters more here than in the
   CHANGELOG. Two sentences rewritten to say "outgoing request body"
   and to name that the failure is always in our chain, not upstream.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvZKP1JeXgHFCovTaAPT5B

* docs: Codex review artifact for PR #325 round 2

* docs: Codex review artifact for PR #325 round 2

* docs: Codex R2 blocker fix + criterion-3 Q5h-block exclusion

Two edits, one commit, both AITL-authored, both pasted verbatim after
union hygiene scan (hostname + operator-path + SSH + UUID + IPv4):

1. CHANGELOG output-guard invariant list: "role alternation" replaced
   with "role validity and system-message placement". checkRoles at
   output-guard.mjs:66-77 does not compare adjacent roles; it rejects
   roles outside user/assistant/system and rejects system at
   messages[0]. Mid-conversation system messages are LEGAL because
   deferred-tool-rewrite injects them. Codex R2's single blocking
   finding; the R1 rewrite enumerated invariants from function names
   and misread this one.

2. Promote-criteria criterion 3: adds a Caveat (a) for the Q5h-block-
   longer-than-TTL case. AITL measured on 2026-08-07 that a 1h53m
   fleet block on a 1h TTL produces 5.5M of 5.6M window cache_creation
   from nine cold-start turns at 0% hit rate — visually indistinguish-
   able from prefix-corruption regression under the criterion's rule.
   Without the exclusion, the first throttled afternoon during the
   soak would read as insertion-normalization busting the prefix, and
   #272's waiver failure-mode-1 would corroborate it into holding a
   good release. Two artifacts inheriting one blind spot is not
   independent confirmation. The exclusion carries the measurement,
   the triage procedure, and the persistence-not-magnitude distinguisher.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GvZKP1JeXgHFCovTaAPT5B

* docs: Codex review artifact for PR #325 round 3

---------

Co-authored-by: vsits-proxy-builder[bot] <279815601+vsits-proxy-builder[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: vsits-codex-review-agent[bot] <279008405+vsits-codex-review-agent[bot]@users.noreply.github.com>
Co-authored-by: Chris Nighswonger <chris.nighswonger@veritassuperaitsolutions.com>
Gunther-Schulz added a commit to Gunther-Schulz/claude-code-cache-fix that referenced this pull request Aug 15, 2026
cnighswonger#281 sat ten days after its dependency merged, and every reading of it
was true about what it looked at: the thread was quiet, so a fresh read
graded it "ball with nobody", and pr-rounds agreed — correctly, by its
own definition, since nobody had posted.

The predicate is the gap, not the reading. A PR becomes actionable
without anyone posting when its blocker clears, and the writer has no
way to see that. Booked READY here rather than folded into the doorbell
RECORD above it: that entry's realizing write is a hook in dotfiles,
while this one resolves to tools/pr-rounds.mjs in this repo — folding it
there would have made it dispatchable by nobody, which is the exact
defect that entry documents about itself.

Noticed while booking, not swept: the entry at BACKLOG.md:9850 says cnighswonger#278
is CONFLICTING and needs a rebase. cnighswonger#278 merged 2026-08-06. Same class,
one instance older.
Gunther-Schulz added a commit to Gunther-Schulz/claude-code-cache-fix that referenced this pull request Aug 15, 2026
…ys ago

The entry was booked 2026-08-05 on a correct DIRTY reading and cnighswonger#278
merged the next day. Nothing in its design was executed; it moves to
the closure home unbuilt rather than being deleted, because the class
is what matters — an entry killed by a MERGE decays in silence, since
nothing posts to the thread and reading the entry cannot reveal it.

The section's state table is replaced rather than appended to: it
claimed four PRs were awaiting upstream that have since merged. Nine
of twelve merged, one closed, two still open, all re-read from the API.
Gunther-Schulz added a commit to Gunther-Schulz/claude-code-cache-fix that referenced this pull request Aug 15, 2026
Population derived, not hand-listed: all 18 PR references in live
sections resolved against the upstream API, 13 closed, and the 14
entries citing a closed one read individually. Eight close, six stay
-- history, a live watch, a park owned by another repo.

Each closes against an executed check rather than a reading of the
entry. The push-scan file-half filter is closed by the guard's own
output during this session's push, not by its commit message. The
cnighswonger#278 rebase round is closed by the merge timestamp that made its held
push moot -- the second cnighswonger#278 entry to close that way today.

Bodies move unedited; the dispositions live in the pass entry. What
the pass did NOT sweep is named in it: entries resting on capture
aliases, live file state or matrix rows are a different stale-risk
class and remain unexamined.
Gunther-Schulz added a commit to Gunther-Schulz/claude-code-cache-fix that referenced this pull request Aug 16, 2026
…eam made to our own PRs

Fork main was 37 behind and drifting. Sized against the merge base (76d586d),
not tree-to-tree: 97 files incoming, +22,197/-782. Twenty-eight files actually
conflicted, not the 56 that "changed on both sides" suggested — `git merge-tree`
is the instrument for that question.

WHY MOST OF IT CONFLICTED AT ALL. Twenty-two of the 28 conflict only because
our own merged upstream PRs re-import our own work as an independent add: none
of those files exists at the merge base, so git sees add/add with no ancestor.
Each has exactly one upstream commit touching it and each is a Gunther Schulz
PR (cnighswonger#272 cnighswonger#273 cnighswonger#275 cnighswonger#278 cnighswonger#279 cnighswonger#280 cnighswonger#282 cnighswonger#306). Checked with --full-history,
because plain `git log -- <path>` prunes under history simplification.

WHERE "TAKE OURS" WOULD HAVE BEEN WRONG — upstream amended these in review of
our own PRs and the fork never took them back. All four are now in:

- upstream-change-detection (cnighswonger#282): a count-only DECREASE alarms again.
  We suppressed both directions, so every compaction and truncation was
  silently absorbed by the one detector whose job is to notice unanticipated
  shape changes. Red-first: with the old rule restored the new bite fails
  alone, 16 and 17 stay green.
- request-capture (cnighswonger#275): capture dir created 0700 (the listing leaks session
  keys through filenames) and boot records route the environment through
  `publishableGates`, so non-allowlisted CACHE_FIX_* VALUES are redacted. We
  dumped all of them, and captures feed harvest, which feeds fixtures in a
  PUBLIC tree. Of the 113 CACHE_FIX_* names this proxy reads, 73 are now
  redacted; nearly all are path-, URL- or credential-valued.
- prefix-diff (cnighswonger#280): the cross-key retention sweep. We had no equivalent
  anywhere and the snapshot dir holds 28,157 files. Ported with tests upstream
  never wrote, because it DELETES and its scope boundary is load-bearing here:
  13,774 of those files are `-canon.json` / `-relocated.json` / `-rungs.json`,
  fork-owned LIVE STATE whose deletion rotates the key its owner reads. Two
  mutations prove the bites — widening the scope regex, and disabling the age
  pass — each goes red on the right ones.
- thinking-block-sanitize (cnighswonger#279): v2's continuation protection is tail-scoped.
  Inert here (CACHE_FIX_THINKING_SANITIZE unset → v1), and v1 is byte-identical
  across a three-case corpus. Our own suite already carried the bite asserting
  upstream's v2 contract and was failing on it.

WHERE TAKING UPSTREAM WOULD HAVE BROKEN PRODUCTION. `proxy/extensions.json` is
purely a formatting conflict — no shared entry's settings differ — but upstream
still rosters `messages-cache-breakpoint`, which exists at the base and which
this fork deleted, entry AND extension file. Resolved to ours exactly.

FOREIGN WORK TAKEN: Junyong Lee's RFC 7230 absolute-form request-targets (cnighswonger#261,
which is what stops the CC auto-updater 404ing through forward mode), the two
ca-trust fixes (cnighswonger#283, cnighswonger#296), the read-dedupe ordering correction (cnighswonger#310),
CHANGELOG, and the regenerated pt-br guide.

FORK ADAPTATIONS, each commented at its site: upstream's new tests assume
upstream's `~/.claude` layout while this fork resolves through XDG (capture
dir, CA dir, quota-status), and upstream's new temp-dir sites are routed
through tools/tmpdir.mjs so the no-raw-mkdtemp guard stays closed rather than
being opened for them. `CACHE_FIX_COALESCE_SIDECAR` is added to the publishable
gate allowlist — it is the one of our 12 serving gates upstream's list did not
cover, and without it the boot record could no longer reproduce the serving
configuration.

ONE THING DELIBERATELY NOT TAKEN. Upstream gates all prompt-text persistence on
CACHE_FIX_PREFIXDIFF_CONTENT, off by default; this fork has no such gate and
always stores system text and message previews. That is a real exposure on a
machine whose proxy fronts every session, and it trades directly against this
fork's byte-level attribution. It is an operator decision, booked, not taken
inside a merge — and the security bite now asserts the fork's actual contract
so it goes red the day the gate is ported.

Leak gate proven still firing after resolution, both arms on one real fixture:
untouched, `absence-scan: clean`, exit 0; with a freshly generated v4 UUID in
`.key`, `FINDING capture-uuid $.key`, exit 2.

Full suite green at this commit: 3514 tests, 3502 pass, 0 fail, 12 skipped.

Restart NOT taken: the incoming set touches state keys and freeze logic, so
row 3's transparency argument does not carry and the boundary is the operator's
to choose. Pin bump owed in dotfiles (proxy/ changed).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012bEWVpDVQu9f5bMaqb2W4t
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved-by-code-agent Final implementation approval from Code Agent approved-by-codex-agent Final implementation approval from Codex Agent approved-by-lead Final implementation approval from project lead ready-for-merge Required reviews are complete and no known blockers remain reviewed-by-codex-agent Directive/spec reviewed by Codex — no blocking findings reviewed-by-lead Reviewed by project lead

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants