JuliusBrussee/caveman · error
cave_budget_denomination_unavailable
cave_budget_denomination_unavailable
Error message
cave_budget_denomination_unavailable
What it means
First ground of the USD-denomination gate: thrown when the budget meter is denominated in USD (budget.maxUsd) but the selected model is not priced in the public catalog (modelIsPriced(provider, id) is false). A USD cap against an unpriced model would meter an honest zero and never bind, so the run refuses to start. The catalog is generated from provider-catalog YAML and prices only region-global rows; regional-only or custom/private models are absent by design.
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 27d5a3981a)
Solutions
- Switch to a model the catalog prices (any standard Anthropic/OpenAI/Google list-priced model)
- Use a token-denominated budget instead: budget: { maxTokens: N } — it needs no price
- Upgrade the package so its generated catalog covers the model
- Check src/catalog.ts (generated) or the provider-catalog YAML to confirm which models carry global USD rows
Example fix
// before
const result = await agent.run(input, {
model: "myprovider/custom-finetune",
budget: { maxUsd: 2 },
});
// after
const result = await agent.run(input, {
model: "myprovider/custom-finetune",
budget: { maxTokens: 500_000 },
}); Defensive patterns
Strategy: validation
Validate before calling
import { modelIsPriced } from "@caveman-ai/agent"; // or read generated catalog
function assertUsdBudgetPriced(budget: unknown, provider: string, modelId: string) {
if (budget && typeof budget === "object" && "maxUsd" in budget &&
!modelIsPriced(provider, modelId)) {
throw new Error(`model ${provider}/${modelId} unpriced; use maxTokens budget`);
}
} Type guard
const isUsdBudget = (b: unknown): b is { maxUsd: number } =>
typeof b === "object" && b !== null && "maxUsd" in b; Prevention
- Check the generated catalog for a global price row before using budget.maxUsd
- Default to maxTokens budgets for custom/newer models
- Re-check pricing compatibility after upgrading the package (catalog regenerates)
When it happens
Trigger: run with { budget: { maxUsd: 5 } } and a model that is new, regional-only, aliased, or private (e.g. a fine-tune or a custom provider/model string) so the catalog has no price row for it.
Common situations: A model released after the catalog was generated; using a regional-only price row (omitted on purpose); a custom model id typo; running on an older package version whose catalog predates the model.
Related errors
- cave_provider_usage_unpriced
- ${label}: ${key} has no pricing block
- ${label}: duplicate region-agnostic row ${key}
- ${label}: no priced region-agnostic rows found
- %s: provider, model, and region are required
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/cc879d60c7ffdd2b.
Report an issue: GitHub.