{"record":{"id":"cc879d60c7ffdd2b","repo":"JuliusBrussee/caveman","slug":"cave-budget-denomination-unavailable-cc879d","errorCode":"cave_budget_denomination_unavailable","errorMessage":"cave_budget_denomination_unavailable","messagePattern":"cave_budget_denomination_unavailable","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/runtime.ts","lineNumber":1242,"sourceCode":"      conversationOriginals,\n    );\n\n    const gatewayURL = resolveGatewayURL(options.gatewayURL);\n    const caveRoute = await resolveCaveRoute(gatewayURL, {\n      ...options,\n      billingProofRequired: budgetMeter?.denomination === \"usd\" && options.streamFn === undefined,\n    }, efficiencyPlan !== undefined);\n    const nestedOptions: InternalRunOptions = { ...options, caveRoute };\n\n    const models = options.models ?? builtinModels();\n    const model = options.model ?? resolveModel(definition, models, options.rootDir ?? process.cwd());\n    // Runtime-gated denomination, first ground: the catalog must\n    // price the model, or a USD cap meters an honest zero and never binds. The\n    // second ground — the credential regime — is checked after routing below,\n    // because which credential pays depends on where the request goes.\n    if (budgetMeter?.denomination === \"usd\" &&\n        !modelIsPriced(model.provider, model.id)) {\n      throw new Error(\"cave_budget_denomination_unavailable\");\n    }\n    // Actual routing is the source of truth, not the route decision: the\n    // gateway only speaks the three provider dialects it proxies, so a model\n    // outside them keeps its own base URL even on a reachable gateway. Every\n    // downstream honesty question — which headers may be sent, what mode this\n    // run may claim — reads gatewayActive, never caveRoute.useGateway alone.\n    const routing = caveRoute.useGateway\n      ? routeModelThroughCave(model, gatewayURL)\n      : { model, routed: false };\n    const routedModel = routing.model;\n    const gatewayActive = routing.routed;\n    // Runtime-gated denomination, second ground: the run must actually be\n    // BILLED in dollars. A Claude Pro/Max subscription reached through Pi's\n    // credential store is not billed per token, so every dollar this ledger\n    // reported for it would be fiction.\n    //\n    // Which credential pays is what decides this. A caller-supplied streamFn\n    // owns its transport, so local login says nothing about billing. A routed","sourceCodeStart":1224,"sourceCodeEnd":1260,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/runtime.ts#L1224-L1260","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nconst result = await agent.run(input, {\n  model: \"myprovider/custom-finetune\",\n  budget: { maxUsd: 2 },\n});\n\n// after\nconst result = await agent.run(input, {\n  model: \"myprovider/custom-finetune\",\n  budget: { maxTokens: 500_000 },\n});","handlingStrategy":"validation","validationCode":"import { modelIsPriced } from \"@caveman-ai/agent\"; // or read generated catalog\nfunction assertUsdBudgetPriced(budget: unknown, provider: string, modelId: string) {\n  if (budget && typeof budget === \"object\" && \"maxUsd\" in budget &&\n      !modelIsPriced(provider, modelId)) {\n    throw new Error(`model ${provider}/${modelId} unpriced; use maxTokens budget`);\n  }\n}","typeGuard":"const isUsdBudget = (b: unknown): b is { maxUsd: number } =>\n  typeof b === \"object\" && b !== null && \"maxUsd\" in b;","tryCatchPattern":null,"preventionTips":["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)"],"tags":["budget","catalog","pricing","usd"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}