claude -p --output-format json returns empty result when final assistant message contains text + tool_use
Description
When using claude -p --output-format json, the result field in the JSON envelope is empty if the model's final assistant message contains both a text block and a tool_use block in the same response. The tool gets executed, the model has nothing more to say, and result is "".
The text IS stored in the session history and is visible when resuming with claude -r <session-id>. It's just not included in the envelope.
This is distinct from the v2.1.83 regression (#38805) where result was always empty — simple text-only responses work correctly on v2.1.104.
Version
- Claude Code v2.1.104
- Linux 6.8.0-107-generic (x86_64)
Steps to Reproduce
claude -p --output-format json --dangerously-skip-permissions --model claude-sonnet-4-6 \
"Write a haiku about mountains, then call TodoWrite with a single completed todo. Output the haiku and tool call in one response. Do not write anything after the tool executes."
Reproduces consistently (3/3 runs tested).
Expected Behavior
result should contain the text content from the assistant's response.
Actual Behavior
{
"result": "",
"stop_reason": "end_turn",
"output_tokens": 291,
...
}
resultis""despite tokens being generated- Resuming the session interactively shows the text was produced
Root Cause
The model produces a single message with two content blocks:
textblock (the actual output)tool_useblock (e.g.TodoWrite)
The CLI executes the tool, receives the tool_result, and the model stops (no further text). The envelope's result field appears to be populated only from a final text-only assistant turn — which doesn't exist in this case.
Impact
Any claude -p caller that relies on the result field will silently receive an empty string when the model happens to end its response with a tool call alongside text output. This is especially likely with agents that use TodoWrite for task tracking.
Workaround
Resume the same session with a follow-up prompt. A naive "repeat your last response" does not work — the agent often doesn't realize it produced text before the tool call, and the recovery itself can trigger the same bug if the agent uses tools again.
What works reliably (tested 5/5 across Opus and Sonnet):
claude -p --output-format json --dangerously-skip-permissions \
--resume <session-id> \
"The text you wrote in your previous message was lost in transit. I can see you wrote something before the tool call, but I cannot read it. Please re-send that exact text, prefixed with RE-SENDING: on the first line. Do not call any tools, just output text."
Key elements:
- Tell the agent its text was "lost in transit" and that you can see it wrote something before the tool call — without this context the agent often responds with "No response requested"
- Ask for a
RE-SENDING:prefix to anchor a text-only response (strip it from the result afterward) - Explicitly say "do not call any tools" to prevent the same bug from recurring
---
🤖 Generated by Claude Code
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗