{"record":{"id":"ef903defbd9033bd","repo":"JuliusBrussee/caveman","slug":"cave-reasoning-usage-unavailable","errorCode":"cave_reasoning_usage_unavailable","errorMessage":"cave_reasoning_usage_unavailable","messagePattern":"cave_reasoning_usage_unavailable","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/runtime.ts","lineNumber":1585,"sourceCode":"            );\n          }\n        }\n        if (reservation !== undefined && reservation.length > 0) {\n          markSpendIncomplete(reservation.map((item) => item.ledger));\n          spendFailure ??= failure;\n        }\n      }\n      finalMessage = message;\n    };\n    const streamFn: StreamFn = async (selected, context, streamOptions) => {\n      if (options.signal?.aborted) {\n        throw options.signal.reason ?? new Error(\"cave_run_aborted\");\n      }\n      if (spendFailure) throw spendFailure;\n      if (usageFailure) throw usageFailure;\n      if (nestedUsage.incomplete) throw new Error(\"cave_nested_usage_incomplete\");\n      if (efficiencyPlan && reasoningUsageUnavailable) {\n        throw new Error(\"cave_reasoning_usage_unavailable\");\n      }\n      if (efficiencyPlan) {\n        enforceSemanticBudgets(contextBill(lowered.ir), outputTokens, efficiencyPlan);\n        if (reasoningTokens > efficiencyPlan.budgets.reasoning) {\n          throw new Error(\"cave_reasoning_budget_exceeded\");\n        }\n      }\n      // The hard model-call ceiling is a stop condition, not a failure: ending\n      // the run through the same graceful path as every other stop keeps the\n      // partial work and the receipt intact. Checked before the\n      // increment so exactly `maxModelCalls` calls are allowed.\n      if (modelCalls >= maxModelCalls) {\n        stopReason = \"call_budget_exhausted\";\n        refusalPending = true;\n        throw new Error(\"cave_run_stopped\");\n      }\n      modelCalls++;\n      // Between-calls stop point. Nothing is in flight here: the previous turn","sourceCodeStart":1567,"sourceCodeEnd":1603,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/runtime.ts#L1567-L1603","documentation":"Thrown before a model call when an efficiency plan is active (candidatePlan/lockedBuild) but the selected model or transport cannot report the reasoning-token split (reasoningUsageUnavailable). Plans budget reasoning tokens explicitly, so a provider that only reports aggregate usage cannot enforce the reasoning budget; the run refuses rather than metering an unknown split as zero.","triggerScenarios":"Running with a candidate plan or locked build on a reasoning-capable model whose provider/adapter does not return a reasoning breakdown, or through a caller streamFn that strips reasoning usage from responses.","commonSituations":"Locking a plan against one model (Anthropic with thinking) then resolving the run to a different model at runtime; a provider API change that stopped returning the reasoning field; an older adapter version that drops the reasoning split.","solutions":["Resolve the run to the exact provider/model the plan was built for (validatePlanSelection already enforces identity — check definition.model)","Upgrade the package so the provider adapter carries the reasoning split for your model","If the model genuinely reports no reasoning, rebuild the plan against a model whose reasoning usage is observable","Do not pass candidatePlan/lockedBuild for ad-hoc runs on models without reasoning reporting"],"exampleFix":"// before\nconst agent = defineAgent({\n  instructions: SYS,\n  model: \"other/no-reasoning-report\",\n  tools: [t],\n  reasoning: true,\n});\nawait agent.run(input, { candidatePlan });\n\n// after\nconst agent = defineAgent({\n  instructions: SYS,\n  model: \"anthropic/claude-with-thinking\", // reports reasoning split\n  tools: [t],\n  reasoning: true,\n});\nawait agent.run(input, { candidatePlan });","handlingStrategy":"try-catch","validationCode":"// Before running with a plan, confirm the resolved model matches the plan's\n// provider/model (identity is what makes reasoning usage observable).\nfunction assertPlanModelMatch(\n  plan: { provider: string; model: string },\n  definition: AgentDefinition,\n  resolvedModel: { provider: string; id: string },\n) {\n  const declared = typeof definition.model === \"string\"\n    ? definition.model\n    : `${resolvedModel.provider}/${resolvedModel.id}`;\n  if (declared !== `${plan.provider}/${plan.model}`) {\n    throw new Error(`plan built for ${plan.provider}/${plan.model}, run resolves ${declared}`);\n  }\n}","typeGuard":"const isPlanModelMatch = (\n  plan: { provider: string; model: string },\n  resolved: { provider: string; id: string },\n): boolean => plan.provider === resolved.provider && plan.model === resolved.id;","tryCatchPattern":"try {\n  await agent.run(input, { candidatePlan });\n} catch (e) {\n  if (e instanceof Error && e.message === \"cave_reasoning_usage_unavailable\") {\n    // re-run without the plan, or re-lock against a model reporting reasoning\n  } else throw e;\n}","preventionTips":["Pin definition.model explicitly when using plans","Verify the provider reports reasoning splits for the chosen model","Keep plan locks and model pins in the same config so they drift together"],"tags":["plans","reasoning","usage"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}