Skip to content

Fix prompt hang when user message replay is absent#423

Closed
timvisher-dd wants to merge 1 commit intoagentclientprotocol:mainfrom
timvisher-dd:timvisher/fix-prompt-replay-hang
Closed

Fix prompt hang when user message replay is absent#423
timvisher-dd wants to merge 1 commit intoagentclientprotocol:mainfrom
timvisher-dd:timvisher/fix-prompt-replay-hang

Conversation

@timvisher-dd
Copy link
Copy Markdown
Contributor

@timvisher-dd timvisher-dd commented Mar 15, 2026

Important

Claude thinks this isn't necessary if #353 merges. I only became aware of the issue because my dev integration branch got reset to upstream/main and didn't have this replayed on top of it.

Commit 87a0886 (#400) introduced a promptReplayed flag to distinguish background task results from the current prompt's result. The flag starts false and only becomes true when the user message is replayed through the query stream with a matching UUID.

In practice, Claude Code either doesn't replay the user message or replays it with a different UUID. This causes promptReplayed to stay false for every prompt, so the result is always discarded as a "background task result". The while(true) loop then blocks forever waiting for a result that will never come — hanging every prompt indefinitely.

Wire logs from two independent sessions confirm:

  1. session/prompt request is sent
  2. Notifications flow normally (stream events, tool calls, agent messages)
  3. stderr shows "consuming background task result" — the result is discarded
  4. No response to the session/prompt request is ever sent
  5. The client hangs forever (no timeout mechanism)

Fix

Replace promptReplayed with backgroundInitPending — a flag that detects the actual fingerprint of a background task completion: a system init message immediately followed by a result with no intervening stream_event or assistant messages.

This works because:

  • Background tasks produce init → result with nothing in between
  • Our prompt's processing produces init → stream_events → result (Claude always streams its response before the result)
  • The stream_event / assistant messages clear backgroundInitPending, so our result is accepted

This approach doesn't depend on user message replay at all, making it robust regardless of whether replay-user-messages is working correctly in the SDK.

Tests added (7 new)

  • Result accepted when no user message replay arrives (the core regression)
  • Result accepted when init + stream events precede result without replay
  • init → result (no activity) correctly consumed as background task, followed by our result with stream events
  • Result accepted when stream events precede result without replay
  • Background task consumed when replay IS present (preserves original Don't get out of sync when background task creates new init/result (try 2) #400 intent)
  • Result accepted when replay has mismatched UUID
  • Existing background task test continues to pass
  • Manual burn-in verification

The promptReplayed flag (87a0886) relied on seeing the user message
replayed with a matching UUID before accepting a result. In practice
Claude Code either doesn't replay the user message or uses a different
UUID, so promptReplayed stayed false and every result was discarded as
a "background task result", hanging the prompt forever.

Replace promptReplayed with backgroundInitPending — a flag that
detects the actual fingerprint of a background task completion:
an init message immediately followed by a result with no intervening
stream_event or assistant messages. Our own prompt's processing always
generates stream events before the result, so the flag gets cleared
and the result is accepted.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@cla-bot cla-bot bot added the cla-signed label Mar 15, 2026
@timvisher-dd timvisher-dd marked this pull request as ready for review March 15, 2026 05:25
@SteffenDE
Copy link
Copy Markdown
Contributor

In practice, Claude Code either doesn't replay the user message or replays it with a different UUID.

How did you determine this? User message replay works just fine for me with the exceptions of compaction and command outputs (where we manually set it to replayed).

a system init message immediately followed by a result with no intervening stream_event or assistant messages.

That's not correct. I've seen Claude emit stream events and assistant messages for background tasks.

@timvisher-dd
Copy link
Copy Markdown
Contributor Author

In practice, Claude Code either doesn't replay the user message or replays it with a different UUID.

How did you determine this? User message replay works just fine for me with the exceptions of compaction and command outputs (where we manually set it to replayed).

I took wire logs from my sessions combined with observations in my agent-shell shell buffers and analyzed them with Claude. I can double check and provide the actual wire logs if that would be helpful. That said the tests that were added here fake the exact message structure that resulted in my broken state.

a system init message immediately followed by a result with no intervening stream_event or assistant messages.

That's not correct. I've seen Claude emit stream events and assistant messages for background tasks.

Definitely possible I misinterpreted the results. I'll try to provide further evidence if it becomes relevant. :)

@benbrandt
Copy link
Copy Markdown
Member

I think this should be fixed on main now?

@SteffenDE
Copy link
Copy Markdown
Contributor

I think so, yes!

@SteffenDE SteffenDE closed this Mar 26, 2026
@SteffenDE
Copy link
Copy Markdown
Contributor

@timvisher-dd please retry with 0.24.1 and report back :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants