{"record":{"id":"b33fd52e991337b5","repo":"JuliusBrussee/caveman","slug":"cave-budget-controller-in-use","errorCode":"cave_budget_controller_in_use","errorMessage":"cave_budget_controller_in_use","messagePattern":"cave_budget_controller_in_use","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/budget.ts","lineNumber":844,"sourceCode":"  }\n\n  private meter(): BudgetMeter {\n    const bound = controllerMeters.get(this);\n    if (bound === undefined) throw new Error(\"cave_budget_controller_unbound\");\n    return bound;\n  }\n}\n\nexport function createBudgetController(): BudgetController {\n  return new BudgetController();\n}\n\n/** Package-internal. Binds a controller to the run that owns its budget. */\nexport function bindBudgetController(\n  controller: BudgetController,\n  meter: BudgetMeter,\n): void {\n  if (controllerMeters.has(controller)) throw new Error(\"cave_budget_controller_in_use\");\n  controllerMeters.set(controller, meter);\n}\n\n/** Package-internal. Releases the binding when the run ends. */\nexport function unbindBudgetController(controller: BudgetController): void {\n  controllerMeters.delete(controller);\n}\n\n/** What the meter says the runtime may do with the next provider call. */\nexport type CallPlan =\n  | { readonly action: \"proceed\"; readonly reservation: BudgetReservation | undefined; readonly outputTokenCap: number }\n  | { readonly action: \"compact\" }\n  | { readonly action: \"stop\"; readonly reason: RunStopReason };\n\n/**\n * The exhaustion ladder's arithmetic: full allowance, else a clamped allowance\n * down to the floor, else exhaustion. `compactionAvailable` lets the caller\n * insert the compaction rung between the full and clamped rungs.","sourceCodeStart":826,"sourceCodeEnd":862,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/agent/src/budget.ts#L826-L862","documentation":"bindBudgetController enforces a one-to-one controller-to-meter mapping held in a WeakMap. This error means you attempted to bind a controller that already has a live meter binding. The library refuses to silently rebind because the existing meter may own budget already spent.","triggerScenarios":"Calling bindBudgetController(controller, meter) a second time on the same controller without an intervening unbindBudgetController — e.g. starting a second run while reusing one controller, or double-invoking run setup on retry paths.","commonSituations":"Concurrent runs sharing a module-level controller singleton; a retry wrapper re-running initialization after a partial failure without unbinding; tests leaking a bound controller across cases.","solutions":["Create a fresh controller with createBudgetController() for each run instead of reusing one","Call unbindBudgetController(controller) when the run ends before any rebind","Verify run teardown always executes (finally/afterEach) so bindings are released","Check for duplicate initialization calls on retry or hot-reload paths"],"exampleFix":"// before\nbindBudgetController(controller, meter1);\nbindBudgetController(controller, meter2); // throws\n// after\nbindBudgetController(controller, meter1);\nunbindBudgetController(controller);\nbindBudgetController(controller, meter2);","handlingStrategy":"try-catch","validationCode":"// track ownership yourself before rebinding\nif (controllerOwner.has(controller)) unbindBudgetController(controller);","typeGuard":null,"tryCatchPattern":"try {\n  bindBudgetController(controller, meter);\n} catch (e) {\n  if (e instanceof Error && e.message === \"cave_budget_controller_in_use\") {\n    unbindBudgetController(controller);\n    bindBudgetController(controller, meter);\n  } else throw e;\n}","preventionTips":["Use one controller per run; create a fresh one each time","Always unbind in a finally block when the run ends","Avoid controller singletons shared across concurrent runs"],"tags":["budget","lifecycle","double-bind"],"backgroundTag":"invalid-state-transition","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"}