Repository navigation
fix(claude): a feed row, kept for the app run, where the proxy transport lost live output - #1442
Conversation
#1381) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#1381) Adds the 'transport-gap' interruption beside #1040's 'transport-error' at every renderer touchpoint (turn state, ledger input statics key, lifecycle candidate, feed item, Feed row, invariants) with the sentence 'Some live output was not captured'. Inert until the fold keeps the value, which needs the package's interruption union (next). The three spelled-out marker checks in ledgerFeedItems become one predicate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… B6) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The owner-approved call (B6 proxy, 2026-09-27) is a DURABLE feed-history row, and 'one row kind'. The work-slot marker from 91b05b0 would give way to the work chip as soon as the agent worked again, which hides the loss. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…utput (#1381) Option B, OWNER-APPROVED (B6 proxy, 2026-09-27): lost data is never hidden. ClaudeSession seals the adapter at the gap (claude-code-headless#69 places it in order); SessionManager records each gap per provider conversation (TransportGapLedger, in memory, bounded) and emits it; the tap forwards session:transport-gap; the initial history chunk carries the conversation's gaps so every feed rebuild gets them back. The renderer merges both paths by id and paints one muted row, 'Part of this response was not captured (HH:MM:SS–HH:MM:SS)', placed by time among the entries through the notice candidates (no new render owner). The fold keeps interruption 'transport-gap' on the sealed turn. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#1445) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…option A Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
OWNER-APPROVED (B6 proxy, 2026-09-27; answers file w3-answers-1628): decision 3 is option A. After a transport gap seals a streaming turn, the working indicator goes idle for the rest of that one response. This is the #1040/#963 rule: the feed never claims work it has no live evidence of. The next request, or the transcript row, brings it back. This was the only UNCONFIRMED item on #1442. The row's lifetime (the app run, not restarts; #1445) is also owner-approved, and quoted verbatim in the body. 🤖 Generated with Claude Code |
… hold lifted) #69 merged after its own round 1 (since is a poll-start lower bound; a request-only flow that provably starts after the gap streams; the phase owner is sealed last). The app API is unchanged; no lockfile entry to resync. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…transcript; distinct live-only ids (#1442 review a) ClaudeSession has no getProviderSessionId, so getNativeConversationId was always null for Claude: every real gap took the live-only path and its row vanished on the first reload (the test fake implemented the getter). The manager now records each committed Claude entry's sessionId, the same value the renderer sends back as providerSessionId, with the resume id as the fallback; cleared at teardown. Live-only ids are a sequence: two gaps of one poll share its until and were merged away. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s flush before a gap row (#1442 review b) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…int and the real Feed row; fix since docs (#1442 review c) - session:load-initial-history's transportGaps attach, through the real preload call and a real ledger (returning the plain chunk: red). - A gap arriving alone repaints a reused adapter (no gaps identity check: red). - The real Feed renders the row's sentence (return null for it: red). - TransportGapRecord and decision 5 say since = the previous poll's start. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Round 1 disposition (head
|
| Finding | Disposition |
|---|---|
a (blocker): no real Claude gap was ever held for its conversation. ClaudeSession has no getProviderSessionId, and the test fake implemented one, so every real gap took the live-only path and vanished on reload. |
Fixed (899a9118). See the details below the table. |
a (major): two gaps of one poll shared a live-only id (…-<until>), so the renderer's merge hid one |
Fixed (899a9118). Live-only ids are a sequence. |
| b (minor): the live feed had no cap; main keeps 50 | Fixed (5d4502d2). One TRANSPORT_GAPS_PER_CONVERSATION in shared types, used by the ledger and mergeTransportGaps (1 red). |
| b/c (survivor): the tap's flush before the row | Pinned (5d4502d2). A buffered semantic event reaches the sink before the row (1 red). |
c (major): the IPC delivery of the rows (session:load-initial-history) was untested |
Pinned (3079e3d0). Driven through the real preload call with a real ledger: the rows ride the chunk, and a conversation that lost nothing gets no key. Returning the plain chunk is 1 red. |
| c (minor): the notice-cache gaps check | Pinned (3079e3d0). A reused adapter repaints for a gap that arrives alone (1 red). |
| c (minor): the real row was never rendered | Pinned (3079e3d0). The real Feed renders the sentence; returning null for the row is 1 red. |
c (minor): stale since docs |
Fixed (3079e3d0). The type doc and decision 5 now say the previous poll's START. |
| Residuals | Stated in the plan's round-1 sections. |
The blocker fix in detail:
- Keying: the manager records the
sessionIdof each committed Claude entry, the same value the renderer sends back asproviderSessionId. It follows/clear. - Fallback: the resume id, before the first entry. The value is cleared at teardown.
- Ruling: no
getProviderSessionIdonClaudeSession, because it would change backend-snapshot and spawn reporting for every Claude pane. - Tests: the fake now matches the real session. Four mutations are each red.
The residuals:
- the
mergeTransportGapssort is a near-equivalent mutant; - the package's no-turn forget (P7) is a test gap in merged claude-code-headless#69;
- b's and c's suspicions are unranked, reasons recorded.
Checks: npx tsc -b clean. The main IPC, sessions, feed and rendering suites pass 438/438.
Next: a capped verification pass for a and c.
🤖 Generated with Claude Code
|
OWNER-APPROVED: both owner-held decisions in this PR were approved by B6 as owner proxy on 2026-09-27. This comment re-records them in the merge gate's format; nothing new is decided here.
🤖 Generated with Claude Code |
…marker # Conflicts: # src/renderer/src/workspace/hook/actions/initialHistory.renderer.test.tsx # src/renderer/src/workspace/hook/actions/initialHistory.ts
ab0c59e
into
integration/batch-2026-09-27-s
…9-27-s Integration batch S (#1442)
Fixes #1381
OWNER-APPROVED (B6 proxy, 2026-09-27; q119 amended by q120), recorded verbatim: "Option B, bounded to the main-process lifetime; restart durability needs a dedicated store and is an owner decision (1445), related to #1235."
The gap row survives later turns in this app run, but not an app restart (see #1445). Within the run it also comes back whenever the conversation's feed is rebuilt: a window reload, an agent reload or crash respawn, or a resume in another pane. It is bounded to 50 gaps per conversation and 500 conversations. The journal's
claude.proxy_transport_gapincident stays a diagnostic copy only.Decision 3 (the spinner after a seal): option A, OWNER-APPROVED (B6 proxy, 2026-09-27). The phase goes idle at the seal, the #1040/#963 rule: the feed never claims work it has no live evidence of. The next request, or the transcript row, brings the indicator back.
Uses claude-code-headless#69, merged. The submodule points at its merge commit
0928344e. There is no lockfile to resync: the app resolves the package through a path alias.What was wrong
transport-gap. The app recorded an incident and re-emitted it, but nothing consumed that.Fix
{since, until};ClaudeProxyAdapter.sealFlowsForTransportGap()stops a streaming turn withinterruption: 'transport-gap', and keeps a tool-awaiting turn's phase, even alongside a concurrent seal;message_start;sinceis the previous poll's start time, a true lower bound (feat(dispatch): attach-all in grid mode + project terminal setting #69 round 1).ClaudeSession: seals the adapter at the gap, before re-emitting it and before any post-gap event.SessionManager:TransportGapLedger, per provider conversation, in memory, 50 per conversation and 500 conversations;{sessionId, gap};since/until.session:transport-gapgoes through the session feed tap, so the remote protocol and phone wire get it as an additive channel;session:load-initial-historyreturns the conversation's gaps inSessionHistoryChunk.transportGaps. That load is the one every feed rebuild makes: a window reload, an agent reload or crash respawn, or a resume in another pane.runtime.transportGapsmerges the live and history paths by record id;collectTransportGapsmakes oneprovider-notice-owner candidate per gap, placed atsince ?? until(no new render owner);transport-gap, rendered as the existing mutedMarkerRow: "Part of this response was not captured (HH:MM:SS–HH:MM:SS)";interruption: 'transport-gap'on the sealed turn.Rulings (see the plan's Execution notes)
/cleardoes not inherit the old row.Tests (fail-first)
npx tsc -bis clean, and the Claude provider, main sessions and workspace-hook suites pass 739/739.claudeSession.suspension.test.ts: the order is event → seal → re-emit → event.sessionManager.proxyGap.test.ts:transportGapLedger.test.ts: order, unique ids, both bounds.transportGapRow.test.ts, driving the REAL adapter → fold → ledger → view bridge:initialHistory.renderer.test.tsx: the real loader restores gaps into a rebuilt runtime, once.useIpcSubscriptions.renderer.test.tsx: the live event is held once per id and ignored for a pane that is gone.npx tsc -bclean. Scoped suites pass 912/912 across 117 files: feed, rendering, session-runtime, sessionFeed, main sessions, Claude runtime, remote, remote-client, and the two hook tests.Boundary and follow-ups
session:transport-gap, so a replay would not show the row.Both are separate surfaces from #1381's desktop feed. #1381 closes with this PR, and these two stay open.
Review round 1 (a FIX, b MERGE-READY, c FIX), each fixed fail-first
ClaudeSessionhas nogetProviderSessionId, so every real gap was live-only and gone on reload; the test fake hid it. The manager now keys each gap by thesessionIdof the Claude conversation's committed JSONL entries, the same value the renderer sends back. It falls back to the resume id before the first entry, and is cleared at teardown. The fake now matches the real session (899a9118).until, so two gaps of one poll are two rows (899a9118).TRANSPORT_GAPS_PER_CONVERSATION, 50) is shared by main and the live feed (5d4502d2).5d4502d2);session:load-initial-historyattach, through the real preload call;Feedrow (3079e3d0).Merge with main
40bf9a38. It was a real conflict with fix(git): a timed-out worktree list is never read as 'no family' #1450 on theinitialHistoryimport line and its test file's closing blocks; both sides were kept.1cfa8c92is a fast-forward.npx tsc -bclean; 2315/2315 across the affected suites.🤖 Generated with Claude Code