Skip to content

fix: defer message_end until all content blocks are closed#37

Merged
dimakis merged 1 commit into
mainfrom
fix/deferred-message-end
Apr 3, 2026
Merged

fix: defer message_end until all content blocks are closed#37
dimakis merged 1 commit into
mainfrom
fix/deferred-message-end

Conversation

@dimakis
Copy link
Copy Markdown
Owner

@dimakis dimakis commented Apr 3, 2026

Summary

The assistant SDK event can fire before all content_block_stop events, especially with streaming-input mode. This caused two bugs:

  • Bug A: blocks lost from finished messagesmessage_end triggered finishCurrent() which froze in-flight blocks with done=false, empty toolInput, no toolResult. Tool pills showed "Running..." permanently and text blocks got orphaned.
  • Bug B: empty tool pillstoolInput and done only arrive on block_end. If block_end races after message_end, the pill never updates.

Both bugs are one fix: track openBlockCount (incremented on content_block_start, decremented on content_block_stop). When assistant arrives, stash the message_end payload and only flush once openBlockCount hits zero.

Diagnosed collaboratively — the Mitzo phone agent identified the root cause during a live debugging session.

Test plan

  • 206 tests pass (1 new: verifies block_end precedes message_end when assistant fires early)
  • tsc --noEmit clean, pre-commit hooks pass
  • Start a chat that triggers tool calls — pills should show name immediately, input on completion, result after execution
  • Verify finished messages retain all blocks (text + tools) after message_end
  • Reattach after iOS background still works

Made with Cursor

The assistant SDK event can arrive before all content_block_stop events
have fired, especially with streaming-input mode. When message_end was
emitted immediately on assistant, finishCurrent() froze in-flight blocks
with done=false, empty toolInput, and no toolResult — tool pills showed
"Running..." permanently and text blocks got orphaned.

Track openBlockCount (incremented on content_block_start, decremented on
content_block_stop). When assistant arrives, stash the message_end payload
and only flush it once openBlockCount reaches zero. This guarantees every
block receives its block_end before the message is finalized.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Made-with: Cursor
@dimakis dimakis merged commit 1739589 into main Apr 3, 2026
1 check passed
@dimakis dimakis deleted the fix/deferred-message-end branch April 3, 2026 00:10
@dimakis dimakis restored the fix/deferred-message-end branch April 3, 2026 00:11
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