JuliusBrussee/caveman · error
cave_reasoning_usage_unavailable
cave_reasoning_usage_unavailable
Error message
cave_reasoning_usage_unavailable
What it means
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.
Source
Thrown at packages/agent/src/runtime.ts:1585
);
}
}
if (reservation !== undefined && reservation.length > 0) {
markSpendIncomplete(reservation.map((item) => item.ledger));
spendFailure ??= failure;
}
}
finalMessage = message;
};
const streamFn: StreamFn = async (selected, context, streamOptions) => {
if (options.signal?.aborted) {
throw options.signal.reason ?? new Error("cave_run_aborted");
}
if (spendFailure) throw spendFailure;
if (usageFailure) throw usageFailure;
if (nestedUsage.incomplete) throw new Error("cave_nested_usage_incomplete");
if (efficiencyPlan && reasoningUsageUnavailable) {
throw new Error("cave_reasoning_usage_unavailable");
}
if (efficiencyPlan) {
enforceSemanticBudgets(contextBill(lowered.ir), outputTokens, efficiencyPlan);
if (reasoningTokens > efficiencyPlan.budgets.reasoning) {
throw new Error("cave_reasoning_budget_exceeded");
}
}
// The hard model-call ceiling is a stop condition, not a failure: ending
// the run through the same graceful path as every other stop keeps the
// partial work and the receipt intact. Checked before the
// increment so exactly `maxModelCalls` calls are allowed.
if (modelCalls >= maxModelCalls) {
stopReason = "call_budget_exhausted";
refusalPending = true;
throw new Error("cave_run_stopped");
}
modelCalls++;
// Between-calls stop point. Nothing is in flight here: the previous turnView on GitHub (pinned to 27d5a3981a)
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
Example fix
// before
const agent = defineAgent({
instructions: SYS,
model: "other/no-reasoning-report",
tools: [t],
reasoning: true,
});
await agent.run(input, { candidatePlan });
// after
const agent = defineAgent({
instructions: SYS,
model: "anthropic/claude-with-thinking", // reports reasoning split
tools: [t],
reasoning: true,
});
await agent.run(input, { candidatePlan }); Defensive patterns
Strategy: try-catch
Validate before calling
// Before running with a plan, confirm the resolved model matches the plan's
// provider/model (identity is what makes reasoning usage observable).
function assertPlanModelMatch(
plan: { provider: string; model: string },
definition: AgentDefinition,
resolvedModel: { provider: string; id: string },
) {
const declared = typeof definition.model === "string"
? definition.model
: `${resolvedModel.provider}/${resolvedModel.id}`;
if (declared !== `${plan.provider}/${plan.model}`) {
throw new Error(`plan built for ${plan.provider}/${plan.model}, run resolves ${declared}`);
}
} Type guard
const isPlanModelMatch = (
plan: { provider: string; model: string },
resolved: { provider: string; id: string },
): boolean => plan.provider === resolved.provider && plan.model === resolved.id; Try / catch
try {
await agent.run(input, { candidatePlan });
} catch (e) {
if (e instanceof Error && e.message === "cave_reasoning_usage_unavailable") {
// re-run without the plan, or re-lock against a model reporting reasoning
} else throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- cave_reasoning_budget_exceeded
- cave_retry_accounting_invalid
- cave_claude_output_budget_too_small_for_reasoning
- cave_claude_reasoning_capability_unknown:${model}
- unknown command ${JSON.stringify(command)}; run caveman-agen
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/ef903defbd9033bd.
Report an issue: GitHub.