max_turns check compares num_turns (one per tool result) against --max-turns (API rounds) — false failures with parallel tool calls

Author: Tsogt-hubCreated Sep 4, 2026Updated Sep 18, 2026
Labelsbugp2

Since #1607 (fix: enforce max turns from claude args, follow-up to #1577), base-action/src/run-claude-sdk.ts throws when resultMessage.num_turns > sdkOptions.maxTurns, even though subtype === "success" and is_error === false:

Claude reported a successful result after 108 turns, exceeding the configured maximum of 80

The two numbers measure different things, so the check produces false failures on runs that completed normally:

  • --max-turns N (CLI) caps the number of agentic API rounds. Claude Code 2.1.259 enforces it (subtype: "error_max_turns" when reached).
  • num_turns in the result message counts one per user-role message, i.e. the initial prompt plus one per tool_result. With parallel tool calls, one API round yields several tool results, so num_turns grows faster than rounds.

Reproduction (Claude Code 2.1.259, local):

bash
claude -p "Run six separate echo commands, one tool call each: echo 1 … echo 6, then say done" \
  --max-turns 3 --output-format json --allowedTools "Bash(echo:*)"

Result: the CLI stops after 3 rounds with subtype: "error_max_turns" and reports num_turns: 4 (3 tool results + prompt). The cap works; num_turns is simply not the number of rounds.

In CI (this action, claude_args: --max-turns 80): the execution artifact of one run shows 36 distinct assistant message.ids (rounds) but 107 tool calls → num_turns: 108 → the action failed with the message above, although the PR and the result comment had already been created. With a model that issues ~3 tool calls per round, most non-trivial runs trip the check; 14 of our 15 failed runs in two days were of this kind (is_error: false, subtype: "success").

I believe #1577 observed the same effect (--max-turns 60num_turns: 73, subtype: "success") and read it as the CLI failing open; the post-hoc check from #1607 then turned that observation into a hard failure.

Suggested fix: compare against the number of assistant rounds (distinct assistant message.id in the SDK stream), or drop the post-hoc check since the CLI already enforces --max-turns and reports error_max_turns.

Workaround we use: --max-turns 240 (≈ 80 rounds × ~3 tool calls) with the job's timeout-minutes as the hard budget.

Source: anthropics/claude-code-action