JuliusBrussee/caveman · error

cave_subagent_unpriced_budget

cave_subagent_unpriced_budget

Error message

cave_subagent_unpriced_budget

What it means

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.

Source

Thrown at packages/agent/src/runtime.ts:4666

function reserveProviderSpend(
  ledgers: readonly SpendLedger[],
  model: Model<Api>,
  requestedOutputTokens: number | undefined,
): SpendReservation[] {
  if (ledgers.length === 0) return [];
  const outputTokens = Math.min(
    model.maxTokens,
    requestedOutputTokens ?? model.maxTokens,
  );
  const ceilingUsd = catalogSearchCeiling(
    `${model.provider}/${model.id}`,
    model.contextWindow,
    outputTokens,
  );
  if (ceilingUsd === undefined) {
    markSpendIncomplete(ledgers);
    throw new Error("cave_subagent_unpriced_budget");
  }
  for (const ledger of ledgers) {
    if (ledger.incomplete) throw new Error("cave_subagent_spend_evidence_incomplete");
    if (ledger.actualUsd + ledger.reservedUsd + ceilingUsd > ledger.limitUsd) {
      throw new Error(ledger.exceededCode);
    }
  }
  const reservations = ledgers.map((ledger) => ({
    ledger,
    ceilingUsd,
    provider: model.provider,
    model: model.id,
  }));
  for (const { ledger } of reservations) ledger.reservedUsd += ceilingUsd;
  return reservations;
}

function settleProviderSpend(

View on GitHub (pinned to 766dce6b13)

Solutions

  1. Point the subagent (and everything it can call) at a model the catalog prices
  2. Remove maxCostUsd from the run if unpriced models are required
  3. Upgrade/regenerate so catalog.ts carries a region:global row for the model
  4. Start a fresh run after fixing: the ledgers were just marked incomplete and stay poisoned

Example fix

// before
const child = parent.subagent({ model: 'custom/preview-model', ... }); // parent run has maxCostUsd

// after
const child = parent.subagent({ model: 'anthropic/claude-sonnet-4-5', ... });
Defensive patterns

Strategy: validation

Validate before calling

// Verify every model a budgeted subagent can resolve is priced before the run
import { modelIsPriced } from '@caveman-ai/agent/src/budget.js';
function assertPricedGraph(models: Array<{ provider: string; id: string }>, costCapUsd: number | undefined) {
  if (costCapUsd === undefined) return;
  const unpriced = models.filter((m) => !modelIsPriced(m.provider, m.id));
  if (unpriced.length > 0) {
    throw new Error(`remove the cost cap or use priced models: ${unpriced.map((m) => m.provider + '/' + m.id).join(', ')}`);
  }
}

Try / catch

try {
  return await dispatchSubagent(task);
} catch (error) {
  if (error instanceof Error && error.message === 'cave_subagent_unpriced_budget') {
    // ledgers now marked incomplete: abort the run; switching models requires a fresh run
    abortRun();
  }
  throw error;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@766dce6b13 (2026-08-18). Data as JSON: /api/errors/11a7a1d03a55b7f4. Report an issue: GitHub.