{"record":{"id":"11a7a1d03a55b7f4","repo":"JuliusBrussee/caveman","slug":"cave-subagent-unpriced-budget","errorCode":"cave_subagent_unpriced_budget","errorMessage":"cave_subagent_unpriced_budget","messagePattern":"cave_subagent_unpriced_budget","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/runtime.ts","lineNumber":4666,"sourceCode":"\nfunction reserveProviderSpend(\n  ledgers: readonly SpendLedger[],\n  model: Model<Api>,\n  requestedOutputTokens: number | undefined,\n): SpendReservation[] {\n  if (ledgers.length === 0) return [];\n  const outputTokens = Math.min(\n    model.maxTokens,\n    requestedOutputTokens ?? model.maxTokens,\n  );\n  const ceilingUsd = catalogSearchCeiling(\n    `${model.provider}/${model.id}`,\n    model.contextWindow,\n    outputTokens,\n  );\n  if (ceilingUsd === undefined) {\n    markSpendIncomplete(ledgers);\n    throw new Error(\"cave_subagent_unpriced_budget\");\n  }\n  for (const ledger of ledgers) {\n    if (ledger.incomplete) throw new Error(\"cave_subagent_spend_evidence_incomplete\");\n    if (ledger.actualUsd + ledger.reservedUsd + ceilingUsd > ledger.limitUsd) {\n      throw new Error(ledger.exceededCode);\n    }\n  }\n  const reservations = ledgers.map((ledger) => ({\n    ledger,\n    ceilingUsd,\n    provider: model.provider,\n    model: model.id,\n  }));\n  for (const { ledger } of reservations) ledger.reservedUsd += ceilingUsd;\n  return reservations;\n}\n\nfunction settleProviderSpend(","sourceCodeStart":4648,"sourceCodeEnd":4684,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/766dce6b1394ebb56a3090748d5a0240a5aefb36/packages/agent/src/runtime.ts#L4648-L4684","documentation":"Before a model call made under subagent SpendLedgers, the runtime computes the worst-case USD ceiling for the call via catalogSearchCeiling(provider/model, contextWindow, outputTokens) (catalog.ts:229). If the catalog has no price row for that model, the ceiling is undefined: a budgeted subagent cannot reserve against a model the catalog cannot price. Every ledger is marked incomplete (markSpendIncomplete) and the call throws before any provider traffic.","triggerScenarios":"A subagent tool under maxCostUsd whose definition (or auto-resolved model) is absent from the generated catalog; regional-only price rows that catalog.ts omits by design; catalog drift after a provider update without regenerating; custom model ids in child definitions while the parent carries the cap.","commonSituations":"Hand-added models in child agents under a parent's maxCostUsd; pinning an old catalog version while providers renamed model ids; unpriced beta/preview models routed into budgeted subagents.","solutions":["Point the subagent (and everything it can call) at a model the catalog prices","Remove maxCostUsd from the run if unpriced models are required","Upgrade/regenerate so catalog.ts carries a region:global row for the model","Start a fresh run after fixing: the ledgers were just marked incomplete and stay poisoned"],"exampleFix":"// before\nconst child = parent.subagent({ model: 'custom/preview-model', ... }); // parent run has maxCostUsd\n\n// after\nconst child = parent.subagent({ model: 'anthropic/claude-sonnet-4-5', ... });","handlingStrategy":"validation","validationCode":"// Verify every model a budgeted subagent can resolve is priced before the run\nimport { modelIsPriced } from '@caveman-ai/agent/src/budget.js';\nfunction assertPricedGraph(models: Array<{ provider: string; id: string }>, costCapUsd: number | undefined) {\n  if (costCapUsd === undefined) return;\n  const unpriced = models.filter((m) => !modelIsPriced(m.provider, m.id));\n  if (unpriced.length > 0) {\n    throw new Error(`remove the cost cap or use priced models: ${unpriced.map((m) => m.provider + '/' + m.id).join(', ')}`);\n  }\n}","typeGuard":null,"tryCatchPattern":"try {\n  return await dispatchSubagent(task);\n} catch (error) {\n  if (error instanceof Error && error.message === 'cave_subagent_unpriced_budget') {\n    // ledgers now marked incomplete: abort the run; switching models requires a fresh run\n    abortRun();\n  }\n  throw error;\n}","preventionTips":["Check modelIsPriced for every model in the subagent graph before starting a maxCostUsd run","Avoid 'auto' model resolution in budgeted subagents - the resolved model may be unpriced","Upgrade the package when adding new models so the generated catalog includes them","Treat unpriced-budget as run-fatal: ledgers are poisoned for the rest of the run"],"tags":["subagent","budget","pricing","catalog","reservation"],"backgroundTag":"unpriced-model-budget","analyzedSha":"766dce6b1394ebb56a3090748d5a0240a5aefb36","analyzedAt":"2026-08-18T03:14:35.516Z","contentChangedAt":"2026-08-18T03:14:35.516Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}