JuliusBrussee/caveman · error · Error
cave_budget_denomination_unavailable
cave_budget_denomination_unavailable
Error message
cave_budget_denomination_unavailable
What it means
A USD-denominated budget (budget: { maxUsd }) requires that the runtime can honestly price the run's model. The runtime-gated denomination check has two grounds: the public catalog must price the provider/model (checked here), and the credential regime must actually bill in dollars (checked after routing). If the selected model has no catalog price, a USD cap would meter an honest zero and never bind, so the run fails closed.
Solutions
- Switch to a model the public catalog prices (verify with modelIsPriced(provider, modelId) before the run).
- Regenerate/upgrade the package so catalog.ts includes the model's USD row (catalog is generated; never hand-edit prices).
- Budget in tokens instead: budget: { maxTokens: n } avoids the USD pricing requirement.
- If the run is gateway-routed or uses a caller-supplied streamFn, ensure the credential gate applies as documented; otherwise drop the USD cap and rely on receipt reporting.
Example fix
// before
runAgent(def, input, { budget: { maxUsd: 5 }, model: "openai/gpt-5-preview-niche" });
// after
if (modelIsPriced("openai", "gpt-5-preview-niche")) {
runAgent(def, input, { budget: { maxUsd: 5 } });
} else {
runAgent(def, input, { budget: { maxTokens: 200_000 } });
} Defensive patterns
Strategy: validation
Validate before calling
import { modelIsPriced } from "@caveman-ai/agent";
if (opts.budget?.maxUsd !== undefined && !modelIsPriced(provider, modelId)) {
// fall back to token denomination or pick a priced model
} Type guard
function usdBudgetIsAdmissible(budget, provider, modelId) {
return budget?.maxUsd === undefined || modelIsPriced(provider, modelId);
} Try / catch
try {
await runAgent(def, input, opts);
} catch (err) {
if (err.message === "cave_budget_denomination_unavailable") {
await runAgent(def, input, {
...opts,
budget: { maxTokens: 200_000 }, // token denomination needs no pricing
});
} else throw err;
} Prevention
- Call modelIsPriced(provider, modelId) before attaching a maxUsd budget.
- Keep the package updated so the generated catalog includes new models.
- Verify the exact provider/model id strings against the catalog — typos look like unpriced models.
- Prefer token budgets for custom, regional-only, or subscription-credential models.
When it happens
Trigger: Setting options.budget with maxUsd (or a USD denomination) while the resolved model is not priced in the generated public catalog (modelIsPriced(provider, id) is false) — e.g. a niche/preview model id, a custom provider string, or a typo'd model id.
Common situations: New or regional-only catalog rows absent from the generated catalog; custom/self-hosted provider ids; subscription (Claude Pro/Max) credential regimes failing the second ground; using an old package version whose catalog predates the model.
Understand the failure class
Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.
Related errors
- cave_subagent_unpriced_budget
- cave_budget_conflicting_cap
- cave_budget_controller_without_budget
- cave_${slot}_budget_exceeded
- : pricing must be an object
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/cc879d60c7ffdd2b.
Report an issue: GitHub.
Appendix: source
Thrown at packages/agent/src/runtime.ts:1242
conversationOriginals,
);
const gatewayURL = resolveGatewayURL(options.gatewayURL);
const caveRoute = await resolveCaveRoute(gatewayURL, {
...options,
billingProofRequired: budgetMeter?.denomination === "usd" && options.streamFn === undefined,
}, efficiencyPlan !== undefined);
const nestedOptions: InternalRunOptions = { ...options, caveRoute };
const models = options.models ?? builtinModels();
const model = options.model ?? resolveModel(definition, models, options.rootDir ?? process.cwd());
// Runtime-gated denomination, first ground: the catalog must
// price the model, or a USD cap meters an honest zero and never binds. The
// second ground — the credential regime — is checked after routing below,
// because which credential pays depends on where the request goes.
if (budgetMeter?.denomination === "usd" &&
!modelIsPriced(model.provider, model.id)) {
throw new Error("cave_budget_denomination_unavailable");
}
// Actual routing is the source of truth, not the route decision: the
// gateway only speaks the three provider dialects it proxies, so a model
// outside them keeps its own base URL even on a reachable gateway. Every
// downstream honesty question — which headers may be sent, what mode this
// run may claim — reads gatewayActive, never caveRoute.useGateway alone.
const routing = caveRoute.useGateway
? routeModelThroughCave(model, gatewayURL)
: { model, routed: false };
const routedModel = routing.model;
const gatewayActive = routing.routed;
// Runtime-gated denomination, second ground: the run must actually be
// BILLED in dollars. A Claude Pro/Max subscription reached through Pi's
// credential store is not billed per token, so every dollar this ledger
// reported for it would be fiction.
//
// Which credential pays is what decides this. A caller-supplied streamFn
// owns its transport, so local login says nothing about billing. A routedView on GitHub (pinned to 3ee70a1026)