JuliusBrussee/caveman · error · Error
cave_subagent_invocation_limit_invalid
cave_subagent_invocation_limit_invalid
Error message
cave_subagent_invocation_limit_invalid
What it means
RunOptions.maxSubagentInvocations caps total subagent admissions across the whole run tree. streamAgentInternal validates it at run start: it must be a safe integer > 0 and at most ABSOLUTE_SUBAGENT_INVOCATION_LIMIT (1,000,000). Any other value throws this error before any model or tool traffic.
Solutions
- Pass a positive safe integer between 1 and 1,000,000 for maxSubagentInvocations.
- Coerce config input first: Number.isSafeInteger(v) check, and Math.floor/parseInt for strings from env or CLI.
- Omit the option entirely if you do not need a tree-wide invocation cap (unset preserves default behavior).
Example fix
// before
await runAgent(def, input, { maxSubagentInvocations: Number(process.env.MAX_SUBAGENTS) });
// after
const n = Number.parseInt(process.env.MAX_SUBAGENTS ?? "", 10);
await runAgent(def, input, Number.isSafeInteger(n) && n > 0 && n <= 1_000_000
? { maxSubagentInvocations: n }
: {}); Defensive patterns
Strategy: validation
Validate before calling
function isValidSubagentInvocationLimit(v) {
return v === undefined || (Number.isSafeInteger(v) && v > 0 && v <= 1_000_000);
} Type guard
function hasValidMaxSubagentInvocations(o) {
return o.maxSubagentInvocations === undefined ||
(Number.isSafeInteger(o.maxSubagentInvocations) && o.maxSubagentInvocations > 0);
} Try / catch
try {
await runAgent(def, input, opts);
} catch (err) {
if (err.message === "cave_subagent_invocation_limit_invalid") {
delete opts.maxSubagentInvocations; // fall back to default behavior
} else throw err;
} Prevention
- Parse numeric options with Number.parseInt and Number.isSafeInteger before passing.
- Never trust raw env/JSON values as numeric options.
- Clamp generated caps to [1, 1_000_000].
When it happens
Trigger: Calling runAgent/streamAgent with options.maxSubagentInvocations set to 0, a negative number, a non-integer (e.g. 1.5), NaN/Infinity, or a number above 1,000,000.
Common situations: Config values read from environment variables or JSON as strings/floats; computing the cap from a formula that yields 0 or fractional values; typos like 10000000 (one zero too many) exceeding the absolute cap.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- cave_subagent_concurrency_limit_invalid
- cave_subagent_depth_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/67a084271e04f100.
Report an issue: GitHub.
Appendix: source
Thrown at packages/agent/src/runtime.ts:1062
controller.abort(error);
return inner.throw(error);
},
[Symbol.asyncIterator]() {
return this;
},
} as AsyncGenerator<CavemanRunEvent>;
}
async function* streamAgentInternal(
definition: AgentDefinition,
input: string,
options: InternalRunOptions,
executionContext: InternalExecutionContext,
): AsyncGenerator<CavemanRunEvent> {
if (options.maxSubagentInvocations !== undefined &&
(!Number.isSafeInteger(options.maxSubagentInvocations) || options.maxSubagentInvocations <= 0 ||
options.maxSubagentInvocations > ABSOLUTE_SUBAGENT_INVOCATION_LIMIT)) {
throw new Error("cave_subagent_invocation_limit_invalid");
}
if (options.maxConcurrentSubagents !== undefined &&
(!Number.isSafeInteger(options.maxConcurrentSubagents) || options.maxConcurrentSubagents <= 0 ||
options.maxConcurrentSubagents > ABSOLUTE_SUBAGENT_INVOCATION_LIMIT)) {
throw new Error("cave_subagent_concurrency_limit_invalid");
}
if (options.lockedBuild !== undefined && options.candidatePlan !== undefined) {
throw new Error("cave_execution_authorization_ambiguous");
}
// 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 === undefinedView on GitHub (pinned to 3ee70a1026)