Skip to content

claude-agent: keep a response that streams during a tool re-list - #1804

Merged
justrach merged 1 commit into
mainfrom
fix/claude-agent-keep-early-events
Nov 2, 2026
Merged

justrach merged 1 commit into
mainfrom
fix/claude-agent-keep-early-events

Conversation

@justrach

@justrach justrach commented Nov 2, 2026

Copy link
Copy Markdown
Owner

Symptom

On Claude Agent, a turn can fail with:

session/prompt: unparseable response: data: {"type":"content_block_delta",...

Root cause

When graff's tool list changes (for example after load_tool_schemas), graff tells Claude Code with notifications/tools/list_changed and waits for the tools/list it sends back (refreshTools / control in src/claude_agent.zig). That wait dropped every non-control line. If Claude Code was already streaming the model's next response (for example after answering a call by itself), its message_start and content_block_start were thrown away. The turn then assembled a body with no message_start, assembleAnthropic returned null, and the JSON fallback failed with "unparseable response".

Fix

  • Lines read while waiting on a control answer go through aside. Stream events and results are kept in order on the session (Kept). A cancel drops its held call, as it does in the turn. Control requests are served as before.
  • turn reads kept lines first (Session.nextLine).
  • Kept lines are freed when the session stops.

Tests

  • scripts/test-claude-agent.py adds a scenario: the model loads a tool, and the fake claude starts streaming its next response before it answers the re-list.
    • Before the fix: the run fails with the same "unparseable response".
    • After the fix: the reply arrives whole, on one process.
  • Unit test for Kept ordering and cleanup.
  • Tier 1 green locally (3620 tests).

While graff waited for Claude Code to re-read its tool list, it dropped
every stream event that arrived. When Claude Code was already streaming
the model's next response, the turn then read that response without its
message_start and failed with "unparseable response". Lines read while
waiting on a control answer are now kept in order and read first by the
turn; a cancel that arrives then drops its held call as in the turn.
@justrach
justrach merged commit 1b6cb8b into main Nov 2, 2026
6 checks passed
@justrach
justrach deleted the fix/claude-agent-keep-early-events branch November 2, 2026 17:34
@justrach justrach mentioned this pull request Nov 2, 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