JuliusBrussee/caveman · error
cave_claude_max_turns_invalid
cave_claude_max_turns_invalid
Error message
cave_claude_max_turns_invalid
What it means
The options.maxTurns value passed to the Claude runtime is validated to be a safe integer strictly between 0 and 1000 (inclusive). Anything else — non-integer, zero, negative, above 1000, or non-numeric — throws this error before the agent runs.
Solutions
- Set maxTurns to a positive safe integer within 1..1000
- Clamp or validate the value before the call: Math.min(1000, Math.max(1, Number(maxTurns)))
- Omit maxTurns entirely to use the runtime default
Example fix
// before
await runClaudeAgent({ ...opts, maxTurns: Number(process.env.MAX_TURNS) });
// after
const turns = Number(process.env.MAX_TURNS);
await runClaudeAgent({ ...opts, maxTurns: Math.min(1000, Math.max(1, Math.trunc(turns))) }); Defensive patterns
Strategy: validation
Validate before calling
if (opts.maxTurns !== undefined && (!Number.isSafeInteger(opts.maxTurns) || opts.maxTurns <= 0 || opts.maxTurns > 1000)) throw new RangeError("maxTurns must be an integer in 1..1000"); Type guard
const isValidMaxTurns = (v) => v === undefined || (Number.isSafeInteger(v) && v > 0 && v <= 1000);
Try / catch
try { await runClaudeAgent(opts); } catch (e) { if (e.message === "cave_claude_max_turns_invalid") { opts.maxTurns = 10; return runClaudeAgent(opts); } throw e; } Prevention
- Clamp user/config-supplied turn counts to 1..1000
- Never pass unparsed strings from env vars
- Omit maxTurns when you want the default
When it happens
Trigger: Calling runClaudeAgent/runClaudeAgentInternal with options.maxTurns set to 0, -1, 1001, NaN, 2.5, or a value exceeding Number.MAX_SAFE_INTEGER.
Common situations: Computing maxTurns from config or user input without clamping; passing 0 intending 'unlimited'; parsing a string like "50" from an env var without conversion.
Understand the failure class
Background: "value must be between 0 and 1" / "out of range" / "must not be negative" errors: fixing range-validation failures across open-source libraries — this error's family across 42 libraries.
Related errors
- cave_claude_header_invalid
- cave_claude_max_budget_invalid
- cave_claude_tool_contract_unsupported
- cave_${field}_invalid
- cave_subagent_concurrency_limit_invalid
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/8e615fcb0fab07e0.
Report an issue: GitHub.
Appendix: source
Thrown at packages/agent/src/claude-runtime.ts:107
throw new Error("cave_claude_candidate_execution_unavailable");
}
if (options.lockedBuild !== undefined) {
throw new Error("cave_claude_locked_execution_unavailable");
}
if (definition.memory !== undefined) {
throw new Error("cave_claude_memory_bridge_unavailable");
}
if (definition.tools.some((item) => item.runtime?.kind === "subagent")) {
throw new Error("cave_claude_subagent_bridge_unavailable");
}
const unsupportedTool = definition.tools.find((item) =>
item.effect !== "read" || item.result !== "inline");
if (unsupportedTool !== undefined) {
throw new Error(`cave_claude_tool_contract_unsupported:${unsupportedTool.name}`);
}
if (options.maxTurns !== undefined &&
(!Number.isSafeInteger(options.maxTurns) || options.maxTurns <= 0 || options.maxTurns > 1_000)) {
throw new Error("cave_claude_max_turns_invalid");
}
if (options.maxBudgetUsd !== undefined &&
(!Number.isFinite(options.maxBudgetUsd) || options.maxBudgetUsd <= 0)) {
throw new Error("cave_claude_max_budget_invalid");
}
const rootDir = resolve(options.rootDir ?? process.cwd());
const lowered = await lowerAgentContext(definition, { rootDir, input });
const bill = contextBill(lowered.ir);
const selected = resolveClaudeModel(definition, rootDir);
const reasoningOptions = claudeReasoningOptions(
selected,
definition.reasoning,
definition.output?.maxTokens,
);
const sandbox = await prepareHarnessToolSandbox(definition, {
rootDir,
...(options.entryPath === undefined ? {} : { entryPath: options.entryPath }),
});View on GitHub (pinned to 3ee70a1026)