Claude Code CLI: --max-budget-usd

Author: Ajra16Created Sep 19, 2026Updated Sep 19, 2026
Labelsbug

Claude Code CLI: --max-budget-usd tripping inside a subagent hangs the query (no ResultMessage)

Component: Claude Code CLI as run by the Python Agent SDK (claude-agent-sdk 0.2.134, bundled CLI 2.1.226; reproduced again on CLI 2.1.259 bundled in SDK 0.2.152, Linux aarch64). Platforms seen: Windows 11 x64 (laptop), Raspberry Pi OS 64-bit / Debian Trixie (Pi 5). Severity: a headless caller never gets a result; only its own wall clock ends the turn.

Summary

When max_budget_usd (CLI --max-budget-usd) is exceeded while a subagent spawned by the Agent tool is running, the parent query emits no ResultMessage (neither success nor error_max_budget_usd). The subagent's own transcript shows it finished its work, but the parent never receives the Agent tool_result and the stream stays open until the client gives up. When the same cap trips on the main thread the query ends normally with subtype: error_max_budget_usd.

Environment

  • Python 3.14, claude-agent-sdk==0.2.134, its bundled claude 2.1.226 (_cli_version.py); also claude-agent-sdk==0.2.152 with bundled 2.1.259 on a Raspberry Pi 5.
  • Auth: claude setup-token (OAuth), not an API key.
  • Options that matter: permission_mode="bypassPermissions", agents={...} with two AgentDefinitions (tools=[...], disallowedTools=[...], model="haiku" in the repro), tools=["Read", "WebSearch", "Agent"], strict_mcp_config=True, two in-process SDK MCP servers, a PreToolUse hook that allows every call in the repro, max_budget_usd=0.02, max_turns=40, setting_sources=[], env={"CLAUDE_CODE_DISABLE_AUTO_MEMORY": "1"}.

Minimal repro (what the 18 Sep 2026 smoke did)

  1. Define one agent, e.g. worker, with a system prompt that tells it to call an MCP tool a few times before answering, and a parent system prompt that says: delegate any question about X to worker through the Agent tool and paste its answer.
  2. Set max_budget_usd=0.02 on the parent options (small enough that the cap trips during the subagent's second or third model call; the parent had spent US$0.0097 at the moment it delegated in our run).
  3. async for message in query(prompt="<a question about X that needs 3 tool calls>", options=options).
  4. Observe the stream: SystemMessages, the parent's AssistantMessage with the Agent tool_use, TaskStartedMessage, the subagent's tool calls (the PreToolUse hook fires for each with agent_type="worker"), then nothing. No TaskNotificationMessage carrying the result, no parent AssistantMessage, no ResultMessage. Our client waited 300 s (laptop) and 420 s (Pi) before cancelling.
  5. Control: the same prompt with max_budget_usd=None completes in ~20 s with ResultMessage(subtype="success"); the same cap on a prompt the parent answers without delegating ends with ResultMessage(subtype="error_max_budget_usd") as documented.

Observed on both CLIs above, three runs each. The subagent's transcript under ~/.claude/projects/<slug>/ ends with its final answer, so the work completed; the loss is in handing the result (or the budget error) back to the parent when the budget check fires inside the subagent's loop.

Not deterministic: a fourth run on CLI 2.1.259 (19 Sep 2026, 01:00 WIB, same max_budget_usd=0.02, the subagent's one tool call finished in a few seconds) came back after 24 s with the partial reply and a ResultError raised by the SDK instead of an error_max_budget_usd ResultMessage, so the caller's generic error path ran ("I hit an error part-way through"). The hang appears when the cap trips while the subagent is mid-loop; when it trips after the subagent has returned, the turn ends, but as a raised error rather than the documented subtype.

Expected

Either the parent receives the Agent tool_result (or an error tool_result saying the budget was exceeded) and then a ResultMessage with subtype="error_max_budget_usd", or the query ends immediately with that ResultMessage. Any terminal message; never an open stream.

Workaround in use

max_budget_usd is left unset when subagents are enabled (TURN_BUDGET_USD=0 in my bot). Bounds that hold: the caller's own asyncio.wait_for wall clock, max_turns, CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH=1 and CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS=2 in the subprocess env. A related observation from the same investigation (19 Sep 2026, CLI 2.1.226): AgentDefinition.maxTurns=2 on the subagent did not stop it either; it ran four tool calls to completion, so per-agent maxTurns is not currently a usable bound from the SDK.

Source: anthropics/claude-agent-sdk-python