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 routed

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Switch to a model the catalog prices (any standard Anthropic/OpenAI/Google list-priced model)
  2. Use a token-denominated budget instead: budget: { maxTokens: N } — it needs no price
  3. Upgrade the package so its generated catalog covers the model
  4. 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

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


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/cc879d60c7ffdd2b. Report an issue: GitHub.