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):
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 60 → num_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.
Since #1607 (
fix: enforce max turns from claude args, follow-up to #1577),base-action/src/run-claude-sdk.tsthrows whenresultMessage.num_turns > sdkOptions.maxTurns, even thoughsubtype === "success"andis_error === false: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_turnsin the result message counts one per user-role message, i.e. the initial prompt plus one pertool_result. With parallel tool calls, one API round yields several tool results, sonum_turnsgrows faster than rounds.Reproduction (Claude Code 2.1.259, local):
Result: the CLI stops after 3 rounds with
subtype: "error_max_turns"and reportsnum_turns: 4(3 tool results + prompt). The cap works;num_turnsis simply not the number of rounds.In CI (this action,
claude_args: --max-turns 80): the execution artifact of one run shows 36 distinct assistantmessage.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 60→num_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.idin the SDK stream), or drop the post-hoc check since the CLI already enforces--max-turnsand reportserror_max_turns.Workaround we use:
--max-turns 240(≈ 80 rounds × ~3 tool calls) with the job'stimeout-minutesas the hard budget.