{"record":{"id":"83ed14a957255580","repo":"JuliusBrussee/caveman","slug":"caveman-agent-run-maxcostusd-must-be-positive","errorCode":null,"errorMessage":"caveman agent: run maxCostUsd must be positive","messagePattern":"caveman agent: run maxCostUsd must be positive","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/runtime.ts","lineNumber":774,"sourceCode":"  costUsd: number;\n  unpriced: boolean;\n  incomplete: boolean;\n  /**\n   * True once any descendant run reported `observe-only`. A graph whose traffic\n   * partly bypassed the gateway cannot be labelled plainly optimized, so the\n   * root under-claims instead of averaging.\n   */\n  observeOnly: boolean;\n};\n\nfunction rootExecutionContext(\n  definition: AgentDefinition,\n  maxCostUsd?: number,\n  options: Pick<RunOptions, \"budget\" | \"breakers\" | \"maxSubagentInvocations\" | \"maxConcurrentSubagents\"> = {},\n): InternalExecutionContext {\n  validateAgentGraph(definition);\n  if (maxCostUsd !== undefined && (!Number.isFinite(maxCostUsd) || maxCostUsd <= 0)) {\n    throw new Error(\"caveman agent: run maxCostUsd must be positive\");\n  }\n  const rootBudget: InvocationLedger[\"rootBudget\"] = maxCostUsd !== undefined\n    ? \"legacy_usd\"\n    : options.budget !== undefined && \"maxUsd\" in options.budget\n      ? \"usd\"\n      : options.budget !== undefined && \"maxTokens\" in options.budget ? \"tokens\" : \"absent\";\n  const invocationTrace = Object.freeze({\n    traceId: randomBytes(16).toString(\"hex\"),\n    spanId: randomBytes(8).toString(\"hex\"),\n    parentSpanId: \"\",\n  });\n  return Object.freeze({\n    rootDefinitionSha256: agentDefinitionSHA256(definition),\n    agentPath: Object.freeze([]),\n    // Root cap is one more ancestor ledger: root turns reserve against it, and\n    // every descendant stacks its own ledger on top of it.\n    spendLedgers: Object.freeze(maxCostUsd === undefined ? [] : [{\n      limitUsd: maxCostUsd,","sourceCodeStart":756,"sourceCodeEnd":792,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/runtime.ts#L756-L792","documentation":"Thrown by rootExecutionContext() when the legacy dollar cap RunOptions.maxCostUsd is present but not a usable number: it must be finite and strictly greater than zero. maxCostUsd is the older error-terminating cost cap; a zero, negative, NaN, or Infinity value is treated as a malformed cap, not 'no cap', so it fails closed before any work starts. Note it also cannot be combined with RunOptions.budget (see cave_budget_conflicting_cap).","triggerScenarios":"Calling run/stream with maxCostUsd: 0, a negative number, NaN, or Infinity. Passing a value read from an unset env var (NaN) or a computed cap that rounds to 0.","commonSituations":"maxCostUsd: Number(process.env.MY_CAP) with the variable unset or empty; a safety 'disable spending' attempt by setting the cap to 0; dividing a budget by a count and getting 0 or a non-finite value.","solutions":["Pass a positive finite number, e.g. maxCostUsd: 5, or omit it entirely to run uncapped","If you meant 'spend nothing', do not run the agent at all — 0 is rejected by design","Validate/normalize values parsed from env vars or config before handing them to the run (Number.isFinite check)","If you also set RunOptions.budget, remove maxCostUsd — the two contracts cannot coexist"],"exampleFix":"// before\nconst cap = Number(process.env.MAX_COST);\nawait agent.run(input, { maxCostUsd: cap }); // NaN when unset\n\n// after\nconst cap = Number(process.env.MAX_COST);\nawait agent.run(input, {\n  ...(Number.isFinite(cap) && cap > 0 ? { maxCostUsd: cap } : {}),\n});","handlingStrategy":"validation","validationCode":"function toPositiveCap(v: unknown): number | undefined {\n  return typeof v === \"number\" && Number.isFinite(v) && v > 0 ? v : undefined;\n}\n// before the run:\nconst maxCostUsd = toPositiveCap(config.maxCostUsd);\nif (config.maxCostUsd !== undefined && maxCostUsd === undefined) {\n  throw new Error(`invalid maxCostUsd: ${String(config.maxCostUsd)}`);\n}","typeGuard":"const isPositiveCap = (v: unknown): v is number =>\n  typeof v === \"number\" && Number.isFinite(v) && v > 0;","tryCatchPattern":"try { await agent.run(input, opts); } catch (e) {\n  if (e instanceof Error && e.message.includes(\"maxCostUsd must be positive\")) {\n    // config bug: fix the cap at its source, do not retry\n  } else throw e;\n}","preventionTips":["Validate numeric options at the config boundary, not at the run call","Never use 0 to mean 'disabled' — omit the option instead","Coerce env strings with Number() and check Number.isFinite before use"],"tags":["budget","validation","run-options"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}