Skip to content

query() iterator ends without result event after subagent + end_turn #339

@devboss777

Description

@devboss777

Summary

The query() async iterator drains cleanly without ever yielding a terminal SDKResultMessage for the main-agent turn when (a) that turn invoked the Agent tool / subagent, AND (b) the entry path supplies a push-based AsyncIterable<SDKUserMessage> with no further user messages buffered. The final assistant message is yielded with stop_reason: "end_turn", then the iterator simply ends. No result, no error.

This is distinct from #333 — that one hangs after the last tool_result; ours ends after a clean end_turn. Same neighborhood (missing terminal event), different surface.

Environment

  • @anthropic-ai/claude-agent-sdk 0.2.76 (we have not yet retested on 0.3.157 — see "Will retest on latest?" below)
  • Subscription auth, model claude-sonnet-4-6
  • Container per run; one query() per process
  • MessageStream is a custom push-based AsyncIterable<SDKUserMessage> that stays open between IPC-arrived user messages, specifically to keep isSingleUserTurn=false and allow subagents to run

Trace evidence

A scheduled-task tick produced this session JSONL (parent-level messages only — subagent abstracted as a single tool_use/tool_result pair):

# type detail
4 assistant thinking only, stop_reason=null (partial)
5 assistant tool_use: Agent (subagent invocation), stop_reason=tool_use
6 user tool_result (subagent return)
7 assistant thinking only, stop_reason=null (partial)
8 assistant ~5,320 output tokens of final text, stop_reason=end_turn

No type: "result" entry follows line 8. The iterator in our consumer ends cleanly (no error thrown, no hang) with resultCount === 0.

The final assistant turn's content is well-formed and complete — the model finished its work. The SDK just never emitted the corresponding SDKResultMessage, so a consumer that relies on result as the signal to flush output gets nothing.

Trigger conditions

Reproduces consistently when all three hold:

  1. The main agent invokes the built-in Agent tool / subagent at least once in the turn
  2. After the subagent returns, the main agent produces additional assistant content (text or further tools)
  3. The driving AsyncIterable<SDKUserMessage> has no follow-up user message buffered when the main agent reaches end_turn

In our deployment this hit the scheduled-task entry path 100% of the time (8+ instances over a multi-hour window). When we rewrote the prompt to forbid the Agent tool and run the same logic inline (single turn, no subagent), the bug stopped reproducing. That is our current production mitigation alongside a defensive fallback emit in our consumer.

Expected vs actual

  • Expected: after stop_reason=end_turn, the SDK yields an SDKResultMessage (subtype success or error_*) and then closes the iterator.
  • Actual: the iterator yields the end_turn assistant message, then closes the iterator. No result message.

Possibly related

Will retest on latest?

Happy to retest on 0.3.157 if helpful — wanted to file with our actual evidence (0.2.76) first since the version delta is significant and didn't want to lose the repro context. The changelog between 0.2.76 and 0.3.157 doesn't mention a fix matching this symptom, but I may have missed it.

I can also share the full session JSONL and a minimal repro harness if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions