{"record":{"id":"67a084271e04f100","repo":"JuliusBrussee/caveman","slug":"cave-subagent-invocation-limit-invalid","errorCode":"cave_subagent_invocation_limit_invalid","errorMessage":"cave_subagent_invocation_limit_invalid","messagePattern":"cave_subagent_invocation_limit_invalid","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/runtime.ts","lineNumber":1062,"sourceCode":"      controller.abort(error);\n      return inner.throw(error);\n    },\n    [Symbol.asyncIterator]() {\n      return this;\n    },\n  } as AsyncGenerator<CavemanRunEvent>;\n}\n\nasync function* streamAgentInternal(\n  definition: AgentDefinition,\n  input: string,\n  options: InternalRunOptions,\n  executionContext: InternalExecutionContext,\n): AsyncGenerator<CavemanRunEvent> {\n  if (options.maxSubagentInvocations !== undefined &&\n      (!Number.isSafeInteger(options.maxSubagentInvocations) || options.maxSubagentInvocations <= 0 ||\n        options.maxSubagentInvocations > ABSOLUTE_SUBAGENT_INVOCATION_LIMIT)) {\n    throw new Error(\"cave_subagent_invocation_limit_invalid\");\n  }\n  if (options.maxConcurrentSubagents !== undefined &&\n      (!Number.isSafeInteger(options.maxConcurrentSubagents) || options.maxConcurrentSubagents <= 0 ||\n        options.maxConcurrentSubagents > ABSOLUTE_SUBAGENT_INVOCATION_LIMIT)) {\n    throw new Error(\"cave_subagent_concurrency_limit_invalid\");\n  }\n  if (options.lockedBuild !== undefined && options.candidatePlan !== undefined) {\n    throw new Error(\"cave_execution_authorization_ambiguous\");\n  }\n  // Budget shape is settled before anything else happens: an ambiguous or\n  // unbounded budget must fail at run() start, not after the first dollar.\n  // maxCostUsd and budget are two different contracts for the same money —\n  // one terminates with an error, the other returns a planned partial result —\n  // so carrying both would leave the run's own stop semantics undecided.\n  if (options.budget !== undefined && options.maxCostUsd !== undefined) {\n    throw new Error(\"cave_budget_conflicting_cap\");\n  }\n  const budgetMeter = executionContext.budgetMeter ?? (options.budget === undefined","sourceCodeStart":1044,"sourceCodeEnd":1080,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/runtime.ts#L1044-L1080","documentation":"Thrown at the start of streamAgentInternal when RunOptions.maxSubagentInvocations is present but invalid: it must be a safe integer, strictly positive, and at most ABSOLUTE_SUBAGENT_INVOCATION_LIMIT (1,000,000). This option bounds monotonic subagent admissions across all tools and depths on one root ledger, so a fractional, zero, negative, or oversized value is rejected before the run does any work.","triggerScenarios":"Setting maxSubagentInvocations to 0 (attempting to 'disable' subagents), a non-integer (1.5), a value above 1,000,000, or a NaN/computed value from config.","commonSituations":"Deriving the limit from a config or env value that defaults to 0 when unset; dividing a budget into counts and passing a fractional result; trying to express 'no subagents allowed' with 0.","solutions":["Pass a positive safe integer within the limit, e.g. maxSubagentInvocations: 8, or omit it to keep prior unbounded behavior","To express 'no subagents', remove subagent tools from the definition instead of passing 0","Clamp computed values: Math.max(1, Math.min(1_000_000, Math.floor(configValue)))"],"exampleFix":"// before\nawait agent.run(input, { maxSubagentInvocations: limit }); // limit = 0 from unset config\n\n// after\nconst limit = Number(process.env.MAX_SUBS);\nawait agent.run(input, {\n  ...(Number.isSafeInteger(limit) && limit > 0\n    ? { maxSubagentInvocations: Math.min(limit, 1_000_000) }\n    : {}),\n});","handlingStrategy":"validation","validationCode":"function clampLimit(v: unknown, max = 1_000_000): number | undefined {\n  return typeof v === \"number\" && Number.isSafeInteger(v) && v > 0 && v <= max\n    ? v : undefined;\n}\nconst opts = { maxSubagentInvocations: clampLimit(config.maxSubs) };","typeGuard":"const isValidInvocationLimit = (v: unknown): v is number =>\n  typeof v === \"number\" && Number.isSafeInteger(v) && v > 0 && v <= 1_000_000;","tryCatchPattern":null,"preventionTips":["Express 'no subagents' by removing subagent tools, never via 0","Clamp config-derived integers with Math.floor and bounds","Keep limits in one typed config object validated at startup"],"tags":["subagents","validation","run-options"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}