Skip to content

fix(transport): forward max_turns=0 to the CLI instead of dropping it#1119

Open
VihaanAgarwal wants to merge 1 commit into
anthropics:mainfrom
VihaanAgarwal:fix/forward-max-turns-zero
Open

fix(transport): forward max_turns=0 to the CLI instead of dropping it#1119
VihaanAgarwal wants to merge 1 commit into
anthropics:mainfrom
VihaanAgarwal:fix/forward-max-turns-zero

Conversation

@VihaanAgarwal

Copy link
Copy Markdown

What breaks

ClaudeAgentOptions(max_turns=0) is silently ignored. The built command omits --max-turns entirely, so the CLI runs with its default (no turn cap) instead of the 0 the caller asked for.

Confirmed against current main:

from claude_agent_sdk import ClaudeAgentOptions
from claude_agent_sdk._internal.transport.subprocess_cli import SubprocessCLITransport

t = SubprocessCLITransport(prompt="hi", options=ClaudeAgentOptions(max_turns=0))
t._cli_path = "/usr/bin/claude"
cmd = t._build_command()
print("--max-turns" in cmd)   # False  (should be True, value "0")

Root cause

_build_command guarded the flag with a truthiness check:

if self._options.max_turns:
    cmd.extend(["--max-turns", str(self._options.max_turns)])

max_turns is typed int | None = None, so None is the "unset" sentinel and 0 is a real caller value. Bare truthiness collapses the two. Every other numeric option in the same method already guards on is not None and forwards 0 correctly: max_budget_usd (0.0 is emitted), task_budget, max_thinking_tokens. max_turns was the lone exception.

A concrete way to hit it: a caller that computes a turn budget and decrements it (options.max_turns = max(0, remaining)) expects 0 to mean "stop", but instead gets an unbounded run.

Fix

Guard on is not None, matching the sibling numeric options, and let the CLI decide what 0 means rather than dropping it in the SDK.

Test

Added test_build_command_max_turns_zero in tests/test_transport.py: asserts --max-turns 0 is emitted for max_turns=0 and that None still emits no flag. It fails on the old truthiness check and passes with the fix. Full suite stays green (ruff, format, mypy clean).

The command builder guarded max_turns with a truthiness check, so
max_turns=0 was treated the same as the None "unset" sentinel and the
--max-turns flag was never emitted. A caller that sets 0 (for example a
turn budget decremented down to zero) silently got the CLI default of no
turn limit instead.

Every other numeric option in _build_command already guards on
`is not None` (max_budget_usd, task_budget, max_thinking_tokens), so 0 is
forwarded there. Match that here and let the CLI decide what 0 means.
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