{"record":{"id":"67949c5796c90908","repo":"JuliusBrussee/caveman","slug":"caveman-agent-subagent-maxcalls-must-be-a-positiv","errorCode":null,"errorMessage":"caveman agent: subagent maxCalls must be a positive integer","messagePattern":"caveman agent: subagent maxCalls must be a positive integer","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/index.ts","lineNumber":175,"sourceCode":"   */\n  maxCostUsd?: number;\n  /**\n   * This child's wallet in tokens — the denomination sibling of `maxCostUsd`,\n   * used by a token-metered run. A token-metered run cannot fund a subagent\n   * that declares no token wallet.\n   */\n  maxTokens?: number;\n  maxContextTokens?: number;\n}): ToolDefinition {\n  const maxInputChars = options.maxInputChars ?? 32_768;\n  if (!Number.isSafeInteger(maxInputChars) || maxInputChars <= 0) {\n    throw new Error(\"caveman agent: subagent maxInputChars must be a positive integer\");\n  }\n  const maxCalls = options.maxCalls ?? 1;\n  const maxCostUsd = options.maxCostUsd ?? 1;\n  const maxContextTokens = options.maxContextTokens ?? 128_000;\n  if (!Number.isSafeInteger(maxCalls) || maxCalls <= 0) {\n    throw new Error(\"caveman agent: subagent maxCalls must be a positive integer\");\n  }\n  if (!Number.isFinite(maxCostUsd) || maxCostUsd <= 0) {\n    throw new Error(\"caveman agent: subagent maxCostUsd must be positive\");\n  }\n  if (options.maxTokens !== undefined &&\n      (!Number.isSafeInteger(options.maxTokens) || options.maxTokens <= 0)) {\n    throw new Error(\"caveman agent: subagent maxTokens must be a positive integer\");\n  }\n  if (!Number.isSafeInteger(maxContextTokens) || maxContextTokens <= 0) {\n    throw new Error(\"caveman agent: subagent maxContextTokens must be a positive integer\");\n  }\n  return tool({\n    name: options.name,\n    description: options.description,\n    input: schema.object({ task: schema.string() }),\n    effect: \"read\",\n    result: \"auto\",\n    ...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),","sourceCodeStart":157,"sourceCodeEnd":193,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/agent/src/index.ts#L157-L193","documentation":"The caveman agent `subagent()` helper validates its options before building the tool. `maxCalls` defaults to 1 and must be a positive safe integer (Number.isSafeInteger and > 0), since it caps how many times the subagent tool may be invoked. The library throws this error eagerly at tool-creation time when the caller passes a bad value such as 0, a negative number, a float, or a value exceeding the safe-integer range.","triggerScenarios":"Calling `subagent({ maxCalls: 0 })`, `subagent({ maxCalls: -1 })`, `subagent({ maxCalls: 2.5 })`, `subagent({ maxCalls: NaN })`, `subagent({ maxCalls: Infinity })`, or a string coerced from user config instead of a number.","commonSituations":"Loading maxCalls from an environment variable, CLI flag, or JSON config as a string without parsing; computing maxCalls with arithmetic that yields NaN; copying an example with `maxCalls: 0` to 'disable' the subagent instead of omitting the tool entirely.","solutions":["Pass a positive integer, e.g. `subagent({ maxCalls: 3 })`, or omit maxCalls to use the default of 1.","If the value comes from config/env, coerce and validate first: `const maxCalls = Number.parseInt(raw, 10)` then check `Number.isSafeInteger(maxCalls) && maxCalls > 0`.","To effectively disable the subagent, do not register it rather than setting maxCalls to 0."],"exampleFix":"// before\nconst tool = subagent({ name: \"researcher\", maxCalls: Number(cfg.subagentCalls) });\n// after\nconst maxCalls = Number.parseInt(cfg.subagentCalls, 10);\nif (!Number.isSafeInteger(maxCalls) || maxCalls <= 0) throw new Error(\"bad subagentCalls config\");\nconst tool = subagent({ name: \"researcher\", maxCalls });","handlingStrategy":"validation","validationCode":"function assertMaxCalls(v) {\n  if (v === undefined) return 1;\n  if (!Number.isSafeInteger(v) || v <= 0) throw new TypeError(`maxCalls must be a positive integer, got ${v}`);\n  return v;\n}\nsubagent({ name: \"researcher\", maxCalls: assertMaxCalls(cfg.maxCalls) });","typeGuard":"const isPosInt = (v) => typeof v === \"number\" && Number.isSafeInteger(v) && v > 0;","tryCatchPattern":"try {\n  const t = subagent({ maxCalls: cfg.maxCalls });\n} catch (e) {\n  if (e.message.includes(\"maxCalls must be a positive integer\")) {\n    console.error(`Config error: subagentCalls='${cfg.maxCalls}' is not a positive integer`);\n    process.exitCode = 2;\n  } else throw e;\n}","preventionTips":["Validate numeric config at load time with a schema library (zod/valibot) using positive-int checks","Never pass raw env-var strings as numeric options; parse and check first","Prefer omitting an option over sentinel values like 0 or Infinity to disable behavior","Add a unit test that constructs every tool with your production config"],"tags":["configuration","validation","argument-error"],"backgroundTag":"invalid-argument-value","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}