Repository navigation
fix(proxy): place a transport gap where the loss was, and seal what it cut - #69
Merged
Merged
Conversation
…t cut (agent-code#1381)
EventsFileTail reports WHERE each lost span sat in its batch (gaps[].index);
ProxyServer emits transport-gap between the events written before and after
it, with the app-clock window {since, until} the lost events were written
in. ClaudeProxyAdapter.sealFlowsForTransportGap() stops a streaming turn as
interruption 'transport-gap', keeps a tool-awaiting turn's phase, and
forgets every tracked flow so post-gap frames are never stitched on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…; seal the phase owner last (cch#69 review b) - A flow that saw only its request is no longer forgotten on a gap: its first post-gap chunk decides. An opening message_start (even split across chunks) streams normally; a mid-SSE start forgets it. Held bytes are replayed raw, so a split multi-byte character survives. - The phase owner is sealed last, so an awaiting-tool owner is no longer handed over to a concurrent streaming flow whose seal published idle. - Pins both of review b's surviving mutations (idle for a first-chunk flow, every concurrent flow sealed). Five mutations, each red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…gaps in one poll (cch#69 review a) since was the previous poll's FINISH time, which could postdate a loss that landed while that poll was still reading, so since..until could be narrower than the loss. It is now the time before that poll read the file. Pins review a's two surviving mutations with a real one-poll, two-settle tail run (a gap index clamped to 1, and only the first gap emitted), plus the since lower bound at the ProxyServer. Three mutations, each red. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…a first-poll gap (cch#69 review c) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Owner
Author
Round 1 disposition (head
|
| Finding | Disposition |
|---|---|
a (major): since was the previous poll's FINISH time, which could postdate a loss that landed mid-poll |
Fixed (a3c335e). since is now when the previous poll started reading. A ProxyServer test on a controlled clock puts the loss inside the previous poll; it failed with expected 200 ≤ 150. |
| a (survivors): a gap index clamped to 1; only the first gap emitted | Pinned (a3c335e). A real one-poll, two-settle tail run: rotations land at the poll's own stat(), giving gaps at indexes 0 and 3. The ProxyServer test emits a, gap, c, d, gap, f, g. Each mutation is 1 red. |
| b (blocker): a request-only flow lost its intact post-gap response | Fixed (5977906). The flow is kept until its first post-gap chunk: an opening message_start (even split across chunks) streams, and a mid-SSE start forgets the flow. The held prefix is replayed as raw bytes; a test splits a chunk inside é, and a string round trip would corrupt it. |
b (major): a concurrent seal cleared a stopped turn's awaiting-tool |
Fixed (5977906). The phase owner is sealed last. Pinned with b's sequence. |
| b/c (survivors): no idle for a first-chunk flow; only the first flow sealed | Pinned (5977906). Each is 1 red. |
c1: the since test was racy |
Superseded: the new controlled-clock test is deterministic. |
| c2: spinner return untested | Pinned (above). |
c3: multi-gap and since: null untested |
Pinned (above), plus a first-poll since: null test (d325558). |
| c4: EVENT_SPEC.md lagged two interruptions | Fixed (d325558). |
Checks: typecheck, contract and vitest --project core all pass. Next: a capped verification pass for a and b.
🤖 Generated with Claude Code
Owner
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs Juliusolsson05/agent-code#1381. The app PR bumps this package and renders a durable "Part of this response was not captured (HH:MM:SS–HH:MM:SS)" feed row. The row is OWNER-APPROVED (B6 proxy, 2026-09-27): option B.
Problem
#64 reports generations deleted unread as
transport-gap, but at the wrong place.ProxyServer.pollEventsOnceemitted the gap before every line of the poll. The lost span actually sits after the held generation's unread tail and before.1/live, and one poll can settle twice.Fix
EventsFilePoll.gaps: Array<{ index, lostGenerations }>says where each lost span sat.settleBelowrecordsout.lines.lengthat entry, andlostGenerationsstays as the total.ProxyServeremitsevents andtransport-gapin written order. The payload is nowTransportGap = { lostGenerations, since, until }. That is the app-clock window the lost events were written in (wire events carry no timestamp):since= the previous poll STARTED reading,until= the gap was seen. Review a: the previous poll's finish time could postdate a loss that landed mid-poll.TransportGapis exported.ClaudeProxyAdapter.sealFlowsForTransportGap()(synchronous):interruption: 'transport-gap';onTransportError;message_start, even split across chunks, the response is whole and streams; one that begins mid-SSE forgets the flow. Held bytes are replayed raw, so a split multi-byte character survives;transport-gapevent, the new method, and theinterruptionrow. The last also gained the missing'transport-error'.Tests (fail-first; each failed before the fix)
eventsFileTail.test.ts:a2, then b lost, c at.1, d live, givinggaps: [{index: 1, lostGenerations: 1}];event:1, event:2, gap, event:4, event:5and thesince/untilwindow.ClaudeProxyAdapter.transportGap.test.ts(5):The frames are synthetic in the recorded shape. Real mitm recordings are private conversation content; same convention as the client-disconnect test.
Mutations are caught: emitting all gaps first (1 red); sealing a stopped turn too (1 red).
Round 1 (a, b: FIX; c: MERGE-READY), each fix fail-first:
eventsFileTail.test.ts:sinceis at or before a loss that landed mid-poll, and both gaps are emitted in place;since: null(c).ClaudeProxyAdapter.transportGap.test.ts(10):message_startsplit across chunks, including a splité, streams intact;sincetaken at the poll's finish;EVENT_SPEC.mdnow lists every interruption (c).npm run typecheckandnpm run test:contractclean;npx vitest run --project corepasses.Coordination
#67 also edits
src/proxy/proxyServer.tsandAPI.md, in different hunks. The two app pointer bumps land in sequence.🤖 Generated with Claude Code