Skip to content

fix: prevent persistent Claude SDK stream hangs - #11

Open
mradwankhalil wants to merge 16 commits into
openchamber:mainfrom
mradwankhalil:fix/compaction-usage-and-summary
Open

fix: prevent persistent Claude SDK stream hangs#11
mradwankhalil wants to merge 16 commits into
openchamber:mainfrom
mradwankhalil:fix/compaction-usage-and-summary

Conversation

@mradwankhalil

@mradwankhalil mradwankhalil commented Sep 3, 2026

Copy link
Copy Markdown

Summary

  • Serialize Claude SDK iterator pulls and enforce one active response pump per persistent bridge.
  • Preserve MCP park/resume events and propagate the active follow-up request AbortSignal through persistent continuations.
  • Ensure watchdogs, client cancellation, and bridge teardown close SDK handles without leaking parked state.
  • Add redacted lifecycle instrumentation and deterministic race/lifecycle regressions.

Validation

  • npm test passed.
  • npm run build passed.
  • npx tsc --noEmit -p tsconfig.json passed.
  • Live proxy matrix passed 13/13 using only claude-opus-5 for Opus, plus sonnet and claude-sonnet-4-6 controls.
  • Live coverage included fresh non-stream, SSE, persistent reuse, two-tool turns, and large prompts.

Live evidence: C:\Users\Zephyrus\AppData\Local\Temp\opencode\opencode-claude-live-matrix-20260912-0316.txt

Scope

All changes in this PR are under opencode-claude. The separate OMO worktree was not modified or included.

@mradwankhalil

Copy link
Copy Markdown
Author

Follow-up fix added after live validation. /compact was lowering OpenCode's local context correctly, but the next normal request resumed the old sticky Claude session, whose hidden pre-compaction context reported ~806k cached input tokens. b4d999 adds the regression; 858a34 clears the base conversation binding at a classified summary boundary so the next turn transfers compacted OpenCode history into a fresh Claude session. Focused regressions, typecheck, build, and diagnostics pass. The full suite still stops at the unchanged Windows smoke assertion in test/smoke.ts:236.

Introduce ClaudePromptInput, a long-lived AsyncIterable<SDKUserMessage>
backed by an unbounded queue. Each push() resolves the next pending
pull; close() releases any waiting consumer. This is the primitive the
proxy uses to stream user input into a persistent Agent SDK Query.
@mradwankhalil mradwankhalil changed the title fix: keep compaction usage per-turn and classify summary updates fix: persist Claude Agent SDK lifecycle across turns Sep 6, 2026
Closing the prompt input tears the SDK stream down synchronously. Doing it
inline meant every turn paid to close the previous turn's bridge before it
could proceed, adding seconds to every request. Measured on Windows with
Opus 5: ~11s for a trivial prompt, ~3s once deferred.

The close still happens, so the stream leak this branch fixes stays fixed —
it just runs on the next macrotask instead of while a request is waiting.
handle.close() and removal from the pool remain synchronous.

Adds test/bridge-teardown-regression.ts covering both teardown paths:
deleteBridge, and putBridge superseding an earlier turn for the same
conversation.
@mradwankhalil
mradwankhalil force-pushed the fix/compaction-usage-and-summary branch from f16b23d to d0cb807 Compare September 6, 2026 18:13
@mradwankhalil mradwankhalil changed the title fix: persist Claude Agent SDK lifecycle across turns fix: prevent persistent Claude SDK stream hangs Sep 12, 2026
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