{"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":"Error","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/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/index.ts#L157-L193","documentation":"Thrown by createSubagent (the tool factory that wraps an agent definition as a callable tool) when the maxCalls option is not a safe positive integer. maxCaps caps how many times the wrapped subagent may be invoked per tool call, so zero or fractional values are meaningless. The check runs at tool-definition time, before any agent execution starts.","triggerScenarios":"Calling the subagent tool factory with options.maxCalls = 0, a negative number, a non-integer like 1.5, NaN, Infinity, or a numeric string such as \"3\". Note the default is 1; the error only fires when you pass an explicit invalid value.","commonSituations":"Computing maxCalls from an env var or config file (strings like process.env.MAX_CALLS arrive as \"5\"), passing a budget-derived float (e.g. cost/price = 2.5), or copying an example that used 0 to mean 'unlimited' (this library has no unlimited mode).","solutions":["Pass an explicit safe integer >= 1, e.g. maxCalls: 3","If the value comes from config/env, coerce and validate first: const maxCalls = Number(raw); Number.isSafeInteger(maxCalls) && maxCalls > 0 ? maxCalls : 1","Round budget-derived floats explicitly: Math.max(1, Math.floor(computed))","Leave maxCalls undefined to accept the default of 1"],"exampleFix":"// before\nconst tool = subagentTool({ agent, name: \"worker\", maxCalls: Number(cfg.maxCalls) }); // cfg.maxCalls = \"5\" or 0\n\n// after\nconst raw = Number(cfg.maxCalls);\nconst tool = subagentTool({\n  agent,\n  name: \"worker\",\n  maxCalls: Number.isSafeInteger(raw) && raw > 0 ? raw : 1,\n});","handlingStrategy":"validation","validationCode":"const maxCalls = Number(cfg.maxCalls);\nif (!Number.isSafeInteger(maxCalls) || maxCalls <= 0) {\n  throw new Error(`config maxCalls must be a positive integer, got ${JSON.stringify(cfg.maxCalls)}`);\n}\nconst t = subagentTool({ agent, name: \"worker\", maxCalls });","typeGuard":"const isPositiveInt = (v: unknown): v is number =>\n  Number.isSafeInteger(v) && (v as number) > 0;","tryCatchPattern":"try {\n  subagentTool({ agent, name: \"worker\", maxCalls });\n} catch (e) {\n  if (e instanceof Error && e.message.includes(\"maxCalls\")) { /* fix config, fail startup */ }\n  throw e;\n}","preventionTips":["Treat subagent options as a validated config object parsed once at startup, not values threaded ad hoc","Never pass raw env/config strings into numeric options without Number() plus Number.isSafeInteger checks","Wrap tool-factory construction in a unit test that builds every tool from sample config files"],"tags":["subagent","validation","configuration","typescript"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}