Skip to content

Poll for recovery after a stream drop instead of failing at the grace (#30)#38

Merged
TheRealDinghyDog merged 1 commit into
opencode-conversionfrom
fix/issue-30-stream-drop-recovery
Jul 10, 2026
Merged

Poll for recovery after a stream drop instead of failing at the grace (#30)#38
TheRealDinghyDog merged 1 commit into
opencode-conversionfrom
fix/issue-30-stream-drop-recovery

Conversation

@TheRealDinghyDog

Copy link
Copy Markdown
Owner

Fixes #30 (review finding M-01).

Problem

The overall turn timeout is 30 minutes, but a closed event stream raced the entire turn: after a 5-second grace, captureTurn performed exactly one recovery attempt and could fail immediately — even while the held-open /message request was still running successfully. The review reproduced a turn returning failure at ~5.787s with error: "terminated" while the response completed at 6.2s.

Fix

Treat stream closure as loss of one observation channel, not turn completion. Once the stream drops:

  1. Wait the grace (now a delay before polling starts, not a deadline) for the held-open response and trailing events to land.
  2. Then actively poll the server for the finished message on a bounded interval (default 2s), continuing until the turn completes another way — response fallback, HTTP recovery, or a reconnected session.idle — or the outer turn timeout fires.

The turn is never failed just because the stream ended. The poll loop is drained in finally so it can't outlive the turn, and it still honors the outer timeout so a genuinely stalled turn ends promptly rather than spinning. The existing post-race single recovery is retained for the non-drop cases (timeout without a drop, empty response).

Tests

captureTurn is exported for testing and driven against a localhost server across three timings:

  • Stream drop + slow successful response (drop at 40ms, response at 500ms, grace 100ms) — the exact M-01 shape; previously failed at the grace, now completes with the answer.
  • Stream drop + lost POST, answer reachable only via GET — the poll loop recovers it (state.recovered === true).
  • Stream drop + response that never lands — bounded by the outer timeout (700ms), asserted to end well under 3s rather than spin.

npm test: 95 passed, 0 failed, 0 skipped.

🤖 Generated with Claude Code

…#30)

A closed event stream raced the whole turn: after a 5s grace,
captureTurn made one recovery attempt and could fail immediately even
while the held-open /message request was still running and about to
succeed (the review reproduced a turn failing at ~5.8s while the response
completed at 6.2s).

Treat stream closure as loss of one observation channel, not turn
completion. After the stream drops, wait the grace for the response and
trailing events to land, then actively poll the server for the finished
message on an interval until the turn completes another way (response
fallback, HTTP recovery, a reconnected session.idle) or the OUTER turn
timeout fires. The turn is never failed merely because the stream ended.

The grace is now a delay before polling starts rather than a deadline,
and a bounded poll interval (default 2s) governs subsequent attempts. The
loop is drained in finally so it cannot poll past return, and it still
honors the outer timeout so a genuinely stalled turn ends promptly
instead of spinning.

captureTurn is exported for tests, which drive it against a localhost
server across three timings: stream drop + slow-but-successful response
(previously failed at the grace), stream drop + lost POST recoverable
only via GET, and stream drop + response that never lands (must end near
the outer timeout).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@TheRealDinghyDog
TheRealDinghyDog merged commit 547a0d2 into opencode-conversion Jul 10, 2026
1 check passed
@TheRealDinghyDog
TheRealDinghyDog deleted the fix/issue-30-stream-drop-recovery branch July 11, 2026 00:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant