{"record":{"id":"2c4f49489f6994fd","repo":"JuliusBrussee/caveman","slug":"cave-budget-controller-without-budget","errorCode":"cave_budget_controller_without_budget","errorMessage":"cave_budget_controller_without_budget","messagePattern":"cave_budget_controller_without_budget","errorType":"error_code","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/runtime.ts","lineNumber":1098,"sourceCode":"  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.\n  if (options.budgetController !== undefined && budgetMeter === undefined) {\n    throw new Error(\"cave_budget_controller_without_budget\");\n  }\n  const breakers = options.breakers === undefined\n    ? undefined\n    : new BreakerState(normalizeRunBreakers(options.breakers, budgetMeter !== undefined));\n  const efficiencyPlan = options.lockedBuild?.selected_plan ?? options.candidatePlan;\n  const buildIdentity = options.lockedBuild === undefined\n    ? undefined\n    : {\n      buildSha256: options.lockedBuild.build_sha256,\n      planSha256: options.lockedBuild.plan_sha256,\n    };\n  const runId = crypto.randomUUID();\n  yield { type: \"run_start\", runId, agentId: definition.id };\n  let conversation: ConversationTransaction | undefined;\n  let activeAbort: (() => void) | undefined;\n  let activeExecution: Promise<void> | undefined;\n  let sandboxSourceSnapshot: SandboxSourceSnapshot | undefined;\n  const pendingSpendReservations: SpendReservation[][] = [];","sourceCodeStart":1080,"sourceCodeEnd":1116,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/agent/src/runtime.ts#L1080-L1116","documentation":"A budgetController releases staged budget tranches, but it is meaningful only when the run actually carries a BudgetMeter (i.e. options.budget was supplied). A controller without a budget would be a silent no-op at the checkpoint that expected it to matter, so the runtime fails closed instead.","triggerScenarios":"Calling runAgent/streamAgent with options.budgetController set while options.budget is undefined (no budget/budgetMeter present in the execution context).","commonSituations":"Reusing a createBudgetController() handle across runs where the new run forgot its budget option; conditional config where the budget block was dropped but the controller stayed; passing a controller for a nested run that inherits no meter.","solutions":["Supply a budget alongside the controller, e.g. budget: { initialUsd: 1, maxUsd: 10 } so checkpoints have tranches to release.","Remove the budgetController option if the run is intentionally unbudgeted.","Construct the controller and budget together in one config object so they cannot drift apart."],"exampleFix":"// before\nrunAgent(def, input, { budgetController: controller });\n// after\nrunAgent(def, input, {\n  budget: { initialUsd: 1, maxUsd: 10 },\n  budgetController: controller,\n});","handlingStrategy":"validation","validationCode":"if (opts.budgetController !== undefined && opts.budget === undefined) {\n  throw new Error(\"budgetController requires a budget\");\n}","typeGuard":"function controllerHasBudget(o) {\n  return o.budgetController === undefined || o.budget !== undefined;\n}","tryCatchPattern":"try {\n  await runAgent(def, input, opts);\n} catch (err) {\n  if (err.message === \"cave_budget_controller_without_budget\") {\n    await runAgent(def, input, {\n      ...opts,\n      budget: { initialUsd: 1, maxUsd: 10 },\n    });\n  } else throw err;\n}","preventionTips":["Always construct budget and budgetController together in one place.","Do not reuse a controller handle in a run that carries no budget option.","Review conditional config builders so dropping budget also drops the controller."],"tags":["agent-runtime","budget","options"],"backgroundTag":"conflicting-config-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"}