Repository navigation
fix(claude): seal an Esc-severed stream, and keep a sleep a sleep (#1040) - #1057
Merged
Merged
Conversation
) Bumps claude-code-headless to its merged fix (#61) and wires the app half. The package: mitmproxy reports a client disconnect only through its `error` hook, which the addon never implemented, so an Esc mid-stream left the flow open holding its phase — a pane stuck on `Thinking` until the next prompt, and a goal loop with no idle edge to resume from. The addon now emits `response-error` (allowed hosts only, flow id and message, nothing else) and the adapter seals the flow: turn stopped, phase idle, flow released. The app: ClaudeSession tells the adapter the suspension instant as soon as it learns one, instead of only when the seal grace expires a minute later. A sleep-severed stream usually reports its own death first, seconds after wake, and whichever signal arrives first decides what the user reads — so without this the turn was reported as a transport failure and the feed lost "Interrupted while asleep". Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…limit Review findings. The fold kept only `system-suspended`, so a stream that died mid-answer — an Esc, a proxy timeout, an upstream failure — left a half-response with no explanation on desktop and phone, which share this pipeline. The cause now survives into the turn, the ledger and a marker row of its own: "Interrupted before the response finished". The wording claims no cause, because mitmproxy's error text cannot establish one. The goal loop's #1040 KNOWN LIMIT is gone with the thing it described. Its test asserted that an Esc-ended turn could only be recovered by typing; it now drives the sealed sequence and asserts Resume recovers the loop, and the comment in `control()` says where the fix lives. The new suspension test's fixtures also failed typechecking (`SystemSuspension` has `source`, not `durationMs`). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Round-2 review. Why a turn stopped is a closed enum — `system-suspended` or `transport-error` — and the feed paints a different marker row for each. Structure-only extraction turned it into `⟨text:15⟩`, the fold then dropped the unrecognised value, and an extracted recording could not reproduce what the user saw. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Closes #1040. Bumps
claude-code-headlessto its merged fix (#61) and wires the app half.The bug
Press Esc while Claude is streaming and Claude Code closes the HTTP connection. mitmproxy reports that only through its
errorhook — the stream tap's end-of-stream call, which becomesresponse-end, runs on a normal end of message. The addon had noerrorhook, so the adapter never tore the flow down and kept the phase it last published.What that looked like:
Thinkinguntil the next prompt;GoalLoopService, because the fix belonged in the package.The package half (#61, merged)
error(flow)emitsresponse-error— allowed hosts only, flow id and mitmproxy's message and nothing else, because this hook fires for flows that never reachedrequest()and a probe captured a full URL with a query token from a host we do not proxy for. The adapter seals the flow as its stale-flow watchdog would, but immediately: turn stopped,finishTurnwith whatever text arrived, phase idle, flow released so a retry drives the UI.Four review rounds also established:
awaiting-toolwhile the tool runs locally, and an upstream socket failure says nothing about whether that tool is still running;Client disconnected.for its own inactivity timeout too (reproduced withtcp_timeout=1), so attributing it to the user would be a guess the UI displays as fact.The app half (this PR)
ClaudeSession.noteSystemSuspensionnow tells the adapter the suspension instant immediately, rather than only when the 60 s seal grace expires (#963). A sleep-severed stream usually reports its own death first — a transport error seconds after wake — and whichever signal arrives first decides what the user is told. Without this the turn was reported as a transport failure, the later seal found no flow left to attribute, and the shared desktop/phone ledger lost "Interrupted while asleep."Tests
ClaudeProxyAdapter.clientDisconnect.test.ts(7 cases) andmitmAddon.error.test.ts, which runs the Python hook itself against a stub mitmproxy and asserts both the payload shape and the silence for a non-allowed host.claudeSession.suspension.test.ts— the adapter is told before the grace, and a session with no proxy still works. Fails without the call.tsc -bclean.🤖 Generated with Claude Code