JuliusBrussee/caveman · error · Error
cave_budget_denomination_unavailable
Error message
cave_budget_denomination_unavailable
What it means
The caller set options.maxBudgetUsd (a USD spend cap) but the SDK's init message reports an apiKeySource that maps to a non-metered credential regime (subscription or unknown) via claudeCredentialRegime(). A USD cap only has meaning when per-token dollar billing is provable; subscription or unknown credentials cannot authorize it, so the run fails closed before any model call.
Source
Thrown at packages/agent/src/claude-runtime.ts:255
const toolCalls: string[] = [];
try {
for await (const message of query) {
if (message.type === "system" && message.subtype === "init") {
initVersion = message.claude_code_version;
// The exact-pin is enforced at the FIRST message the SDK emits, before
// it drives any model call, so a version mismatch costs nothing rather
// than being caught only after the whole run has drained and spent
// `finally` closes the query.
if (initVersion !== CLAUDE_CODE_VERSION) {
throw new Error("cave_harness_upstream_version_mismatch");
}
credentialRegime = claudeCredentialRegime(message.apiKeySource);
// apiKeySource is emitted on init before the SDK drives a model call.
// It is the credential the SDK actually selected, unlike ambient env
// presence. A subscription or unknown regime cannot authorize a USD
// cap because no per-token dollar charge is proven.
if (options.maxBudgetUsd !== undefined && credentialRegime !== "metered") {
throw new Error("cave_budget_denomination_unavailable");
}
assistantModel ??= message.model;
}
if (message.type === "assistant") {
assistantModel = message.message.model;
for (const block of message.message.content) {
if (block.type === "tool_use") toolCalls.push(unprefixClaudeTool(block.name));
}
}
if (message.type === "result") result = message;
}
} finally {
query.close?.();
}
if (initVersion === undefined) {
// The SDK never announced its version — the exact-pin cannot be proven,
// so this fails closed the same as a mismatch.
throw new Error("cave_harness_upstream_version_mismatch");View on GitHub (pinned to 27d5a3981a)
Solutions
- Set ANTHROPIC_API_KEY to a metered API key in the environment the agent runs in, and remove the subscription credential the SDK is currently selecting.
- Drop options.maxBudgetUsd if you intentionally run on a subscription and want the run to proceed without a dollar cap.
- Run `claude doctor` / inspect the init message's apiKeySource to see which credential the CLI actually selected, then fix the auth setup accordingly.
Example fix
// before: subscription credential + USD cap → throws on init
await run({ prompt, maxBudgetUsd: 1.5 });
// after: either supply a metered key…
process.env.ANTHROPIC_API_KEY = meteredKey;
await run({ prompt, maxBudgetUsd: 1.5 });
// …or omit the cap on subscription auth
await run({ prompt }); Defensive patterns
Strategy: validation
Validate before calling
function meteredCredentialLikely(): boolean {
// The definitive source is the init message's apiKeySource, but absence of a
// subscription login plus a present ANTHROPIC_API_KEY is a strong precondition.
return Boolean(process.env.ANTHROPIC_API_KEY) && !hasClaudeSubscriptionLogin();
}
function hasClaudeSubscriptionLogin(): boolean {
// subscription token stored by `claude login`, location varies by OS
return existsSync(join(homedir(), ".claude", "credentials.json"));
} Try / catch
try {
await run({ ...options, maxBudgetUsd });
} catch (error) {
if (error instanceof Error && error.message === "cave_budget_denomination_unavailable") {
// Subscription credential cannot back a USD cap — drop the cap or switch to a metered key.
return run({ ...options }); // no maxBudgetUsd
}
throw error;
} Prevention
- Only set maxBudgetUsd in environments that authenticate with a metered ANTHROPIC_API_KEY.
- Run `claude logout` (or clear the stored subscription credential) on machines that must run budgeted agents.
- Treat this error as fail-closed by design: do not retry with the same options; change the credential or the cap.
When it happens
Trigger: Calling the Claude run API with maxBudgetUsd defined while the environment authenticates with a Claude subscription (OAuth/login) or an unrecognizable credential, i.e. apiKeySource on init is anything but a metered API key. Checked on the init message, so it throws before the first model turn.
Common situations: Developer machine logged into Claude Pro/Max via `claude login` while the config requests a dollar budget; CI missing ANTHROPIC_API_KEY so the SDK falls back to a stored subscription token; a new apiKeySource string the regime classifier doesn't recognize after a CLI update.
Related errors
- cave_claude_reasoning_capability_unknown:${model}
- cave_sandbox_credential_env_not_allowlisted
- cave_sandbox_credential_missing
- cave_breaker_retry_requires_budget
- cave_breaker_retry_spend_invalid
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/5fb87a892777b040.
Report an issue: GitHub.