{"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":"validation","errorClass":null,"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/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/runtime.ts#L1080-L1116","documentation":"Thrown at run start when RunOptions.budgetController is supplied without RunOptions.budget. A controller exists only to release additional tranches up to a budget's max at developer-controlled checkpoints; with no budget meter bound it would be a silent no-op at exactly the checkpoint that expected it to matter, so the run fails closed.","triggerScenarios":"Passing { budgetController: createBudgetController(...) } while omitting budget, or setting budget only in some code paths (e.g. only when a flag is set) while always setting the controller.","commonSituations":"Refactoring staged budgeting: the controller wiring survives but the budget field is dropped during cleanup; conditionally disabling the budget via destructuring while leaving the controller in the options object.","solutions":["Add the budget the controller releases into: budget: { maxUsd: X, initialUsd: Y } (or maxTokens/initialTokens)","Or remove budgetController when running without a budget","Keep controller and budget sourced from the same config branch so they are set or unset together"],"exampleFix":"// before\nawait agent.run(input, { budgetController: controller });\n\n// after\nawait agent.run(input, {\n  budget: { maxUsd: 10, initialUsd: 2 },\n  budgetController: controller,\n});","handlingStrategy":"validation","validationCode":"const budgeted = config.budget !== undefined;\nconst opts = budgeted\n  ? { budget: config.budget, budgetController: controller }\n  : {}; // controller only when budget is set","typeGuard":"const controllerIsValid = (o: { budget?: unknown; budgetController?: unknown }): boolean =>\n  o.budgetController === undefined || o.budget !== undefined;","tryCatchPattern":null,"preventionTips":["Set budget and budgetController from the same config branch","Treat a controller without a budget as a wiring bug caught in code review","Keep staged-budget setup (budget + initialUsd + controller) in one helper function"],"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"}