{"record":{"id":"f03f488a80f2401a","repo":"JuliusBrussee/caveman","slug":"cave-budget-conflicting-cap","errorCode":"cave_budget_conflicting_cap","errorMessage":"cave_budget_conflicting_cap","messagePattern":"cave_budget_conflicting_cap","errorType":"error_code","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/runtime.ts","lineNumber":1078,"sourceCode":"      (!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\n    ? undefined\n    : new BudgetMeter(normalizeRunBudget(options.budget)));\n  if (options.deadlineMs !== undefined &&\n      (!Number.isSafeInteger(options.deadlineMs) || options.deadlineMs <= 0)) {\n    throw new Error(\"cave_run_deadline_invalid\");\n  }\n  if (options.maxSubagentDepth !== undefined &&\n      (!Number.isSafeInteger(options.maxSubagentDepth) || options.maxSubagentDepth <= 0 ||\n        options.maxSubagentDepth > ABSOLUTE_SUBAGENT_DEPTH_LIMIT)) {\n    throw new Error(\"cave_subagent_depth_limit_invalid\");\n  }\n  const deadlineAt = executionContext.deadlineAt ?? (options.deadlineMs === undefined\n    ? undefined\n    : performance.now() + options.deadlineMs);\n  // A controller with nothing to release would be a silent no-op at the\n  // checkpoint that expected it to matter.","sourceCodeStart":1060,"sourceCodeEnd":1096,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/agent/src/runtime.ts#L1060-L1096","documentation":"maxCostUsd (the older error-terminating cap) and options.budget (the reserve-and-clamp budget contract that returns a planned partial result) are two different stop-semantics contracts for the same money. Carrying both would leave the run's own stop behavior undecided, so streamAgentInternal rejects the combination at start, before any spend.","triggerScenarios":"Calling runAgent/streamAgent with both options.maxCostUsd and options.budget defined in the same RunOptions object.","commonSituations":"Merging an options object that sets maxCostUsd with a newer config layer that sets budget; incremental migration from the legacy maxCostUsd API to the budget API where the old field was never removed.","solutions":["Delete maxCostUsd and express the cap via budget: { maxUsd } (or maxTokens) — this gives reserve-and-clamp semantics.","Alternatively keep maxCostUsd for hard error-terminating behavior and remove budget.","Audit config merge code to ensure only one cap style survives the merge."],"exampleFix":"// before\nrunAgent(def, input, { maxCostUsd: 5, budget: { maxUsd: 10 } });\n// after\nrunAgent(def, input, { budget: { maxUsd: 10 } });","handlingStrategy":"validation","validationCode":"if (opts.maxCostUsd !== undefined && opts.budget !== undefined) {\n  throw new Error(\"maxCostUsd and budget are mutually exclusive\");\n}","typeGuard":"function hasSingleCostCap(o) {\n  return !(o.maxCostUsd !== undefined && o.budget !== undefined);\n}","tryCatchPattern":"try {\n  await runAgent(def, input, opts);\n} catch (err) {\n  if (err.message === \"cave_budget_conflicting_cap\") {\n    const { maxCostUsd, ...rest } = opts;\n    await runAgent(def, input, rest); // prefer budget semantics\n  } else throw err;\n}","preventionTips":["Standardize on one cap style (prefer budget) across your codebase.","Strip legacy maxCostUsd during config merges/migrations.","Document the intended stop semantics (error vs planned partial result) in your run wrappers."],"tags":["agent-runtime","budget","options"],"backgroundTag":"mutually-exclusive-options","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"}