JuliusBrussee/caveman · error
cave_subagent_depth_limit_invalid
cave_subagent_depth_limit_invalid
Error message
cave_subagent_depth_limit_invalid
What it means
Thrown at run start when RunOptions.maxSubagentDepth is present but invalid: it must be a safe integer, strictly positive, and at most ABSOLUTE_SUBAGENT_DEPTH_LIMIT (8). Depth defaults to 2; a zero, negative, fractional, or >8 value is rejected before any provider call.
Source
Thrown at packages/agent/src/runtime.ts:1090
// Budget shape is settled before anything else happens: an ambiguous or
// unbounded budget must fail at run() start, not after the first dollar.
// maxCostUsd and budget are two different contracts for the same money —
// one terminates with an error, the other returns a planned partial result —
// so carrying both would leave the run's own stop semantics undecided.
if (options.budget !== undefined && options.maxCostUsd !== undefined) {
throw new Error("cave_budget_conflicting_cap");
}
const budgetMeter = executionContext.budgetMeter ?? (options.budget === undefined
? undefined
: new BudgetMeter(normalizeRunBudget(options.budget)));
if (options.deadlineMs !== undefined &&
(!Number.isSafeInteger(options.deadlineMs) || options.deadlineMs <= 0)) {
throw new Error("cave_run_deadline_invalid");
}
if (options.maxSubagentDepth !== undefined &&
(!Number.isSafeInteger(options.maxSubagentDepth) || options.maxSubagentDepth <= 0 ||
options.maxSubagentDepth > ABSOLUTE_SUBAGENT_DEPTH_LIMIT)) {
throw new Error("cave_subagent_depth_limit_invalid");
}
const deadlineAt = executionContext.deadlineAt ?? (options.deadlineMs === undefined
? undefined
: performance.now() + options.deadlineMs);
// A controller with nothing to release would be a silent no-op at the
// checkpoint that expected it to matter.
if (options.budgetController !== undefined && budgetMeter === undefined) {
throw new Error("cave_budget_controller_without_budget");
}
const breakers = options.breakers === undefined
? undefined
: new BreakerState(normalizeRunBreakers(options.breakers, budgetMeter !== undefined));
const efficiencyPlan = options.lockedBuild?.selected_plan ?? options.candidatePlan;
const buildIdentity = options.lockedBuild === undefined
? undefined
: {
buildSha256: options.lockedBuild.build_sha256,
planSha256: options.lockedBuild.plan_sha256,View on GitHub (pinned to 27d5a3981a)
Solutions
- Use a value in 1..8, e.g. maxSubagentDepth: 3, or omit it for the default of 2
- To prevent subagents entirely, remove subagent tools from the definition (depth 0 is not expressible)
- Clamp config-driven values: Math.min(8, Math.max(1, Math.floor(d)))
Example fix
// before
await agent.run(input, { maxSubagentDepth: 12 });
// after
await agent.run(input, { maxSubagentDepth: Math.min(8, 12) }); // or 8 max Defensive patterns
Strategy: validation
Validate before calling
const depth = Math.min(8, Math.max(1, Math.floor(config.depth ?? 2)));
await agent.run(input, { maxSubagentDepth: depth }); Type guard
const isValidDepth = (v: unknown): v is number => typeof v === "number" && Number.isSafeInteger(v) && v > 0 && v <= 8;
Prevention
- Remember the absolute depth limit is 8 and the default is 2
- Forbid subagents by removing subagent tools, not by depth 0
- Validate depth in the same place you validate other run-option integers
When it happens
Trigger: Setting maxSubagentDepth: 0 (trying to forbid nesting), a non-integer, or a value like 10 that exceeds the hard limit of 8.
Common situations: Assuming deeper recursion is fine and configuring 10 levels; passing a depth derived from config that defaults to 0; trying to express 'no subagents' via depth 0.
Related errors
- cave_subagent_invocation_limit_invalid
- caveman agent: run maxCostUsd must be positive
- cave_subagent_concurrency_limit_invalid
- cave_run_deadline_invalid
- cave_budget_controller_without_budget
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/c22d8e6b4b56dd0c.
Report an issue: GitHub.