JuliusBrussee/caveman · error · Error
cave_subagent_depth_limit_invalid
cave_subagent_depth_limit_invalid
Error message
cave_subagent_depth_limit_invalid
What it means
RunOptions.maxSubagentDepth bounds how deep nested subagent invocation may go (default 2). streamAgentInternal validates it: a safe integer > 0 and at most ABSOLUTE_SUBAGENT_DEPTH_LIMIT. Values outside that range throw at run start before any spend.
Solutions
- Pass a positive safe integer within the absolute depth cap (e.g. 1–3).
- Omit the option to use the default depth of 2.
- Clamp untrusted input: Math.min(ABSOLUTE_LIMIT, Math.max(1, Math.floor(v))).
Example fix
// before
runAgent(def, input, { maxSubagentDepth: 0 });
// after
runAgent(def, input, { maxSubagentDepth: 3 }); Defensive patterns
Strategy: validation
Validate before calling
function isValidSubagentDepth(v) {
return v === undefined || (Number.isSafeInteger(v) && v > 0);
} Type guard
function hasValidMaxSubagentDepth(o) {
return o.maxSubagentDepth === undefined ||
(Number.isSafeInteger(o.maxSubagentDepth) && o.maxSubagentDepth > 0);
} Try / catch
try {
await runAgent(def, input, opts);
} catch (err) {
if (err.message === "cave_subagent_depth_limit_invalid") {
await runAgent(def, input, { ...opts, maxSubagentDepth: 2 }); // library default
} else throw err;
} Prevention
- Rely on the default depth of 2 unless you specifically need deeper nesting.
- Clamp user/config input into [1, absolute limit] before assignment.
- Never encode "unlimited" as 0 or a sentinel huge number.
When it happens
Trigger: Passing maxSubagentDepth as 0, negative, non-integer, NaN/Infinity, or a value exceeding the absolute depth limit (greater than ABSOLUTE_SUBAGENT_DEPTH_LIMIT).
Common situations: Config-driven depth from env vars parsed loosely; trying to express "unlimited depth" with 0 or a huge number instead of omitting the option; arithmetic producing fractional values.
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_subagent_concurrency_limit_invalid
- cave_subagent_invocation_limit_invalid
- cave_budget_conflicting_cap
- cave_budget_controller_without_budget
- cave_claude_max_turns_invalid
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/c22d8e6b4b56dd0c.
Report an issue: GitHub.
Appendix: 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 3ee70a1026)