JuliusBrussee/caveman · error · Error

cave_claude_reasoning_capability_unknown:${model}

Error message

cave_claude_reasoning_capability_unknown:${model}

What it means

claudeThinkingCapability() classifies models into "manual", "adaptive", or "unknown" by regex. Reasoning is enabled (not "off") but the resolved model matches neither regex, so the framework does not know how to configure thinking for it and fails closed rather than guessing a thinking mode that might error or silently no-op upstream. The model id is embedded in the message.

Source

Thrown at packages/agent/src/claude-runtime.ts:518

  reasoning: AgentDefinition["reasoning"],
  outputMaxTokens: number | undefined,
): Pick<ClaudeSDKOptions, "thinking" | "effort"> {
  if (reasoning === "off") return { thinking: { type: "disabled" } };
  const capability = claudeThinkingCapability(model);
  if (capability === "adaptive") {
    return {
      thinking: { type: "adaptive" },
      effort: claudeEffort(reasoning),
    };
  }
  if (capability === "manual") {
    const budgetTokens = reasoning === "high" ? 8_192 : reasoning === "medium" ? 4_096 : 1_024;
    if (outputMaxTokens !== undefined && outputMaxTokens <= budgetTokens) {
      throw new Error("cave_claude_output_budget_too_small_for_reasoning");
    }
    return { thinking: { type: "enabled", budgetTokens } };
  }
  throw new Error(`cave_claude_reasoning_capability_unknown:${model}`);
}

function claudeThinkingCapability(model: string): "adaptive" | "manual" | "unknown" {
  if (/^claude-(?:haiku-4-5|sonnet-4-5|opus-4-(?:1|5))(?:-\d{8})?$/.test(model)) {
    return "manual";
  }
  if (/^claude-(?:sonnet|opus)-4-[678](?:-\d{8})?$/.test(model) ||
      /^claude-(?:fable|mythos|sonnet|opus)-5(?:-\d+)?$/.test(model)) {
    return "adaptive";
  }
  return "unknown";
}

// Accepts ANY result subtype: error subtypes (error_max_turns, …) carry the
// same provider `usage` a success does, so the receipt can be built from a
// failed run too.
type ClaudeCredentialRegime = "metered" | "subscription" | "unknown";

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Use a known model family: manual (claude-haiku-4-5, claude-sonnet-4-5, claude-opus-4-1, claude-opus-4-5) or adaptive (claude-sonnet/opus-4-6/4-7/4-8, claude-fable/mythos/sonnet/opus-5).
  2. Check the embedded model id in the message for typos against those exact patterns (note the optional 8-digit date suffix).
  3. If the model is genuinely new, upgrade the caveman agent package so claudeThinkingCapability knows it, or set reasoning: "off" until then.

Example fix

// before
await run({ prompt, model: "anthropic/claude-3-5-sonnet", reasoning: "medium" });
// → cave_claude_reasoning_capability_unknown:claude-3-5-sonnet

// after
await run({ prompt, model: "anthropic/claude-sonnet-4-5", reasoning: "medium" });
Defensive patterns

Strategy: validation

Validate before calling

const KNOWN_THINKING_MODEL =
  /^claude-(?:haiku-4-5|sonnet-4-5|opus-4-(?:1|5))(?:-\d{8})?$/;
const KNOWN_ADAPTIVE_MODEL =
  /^claude-(?:sonnet|opus)-4-[678](?:-\d{8})?$/ ||
  /^claude-(?:fable|mythos|sonnet|opus)-5(?:-\d+)?$/;
function reasoningSupported(model: string): boolean {
  return KNOWN_THINKING_MODEL.test(model) || KNOWN_ADAPTIVE_MODEL.test(model);
}

Type guard

function supportsReasoning(model: string): boolean {
  return /^claude-(?:haiku-4-5|sonnet-4-5|opus-4-(?:1|5))(?:-\d{8})?$/.test(model)
    || /^claude-(?:sonnet|opus)-4-[678](?:-\d{8})?$/.test(model)
    || /^claude-(?:fable|mythos|sonnet|opus)-5(?:-\d+)?$/.test(model);
}

Try / catch

try {
  await run({ ...options, reasoning: "medium" });
} catch (error) {
  if (error instanceof Error && error.message.startsWith("cave_claude_reasoning_capability_unknown:")) {
    const model = error.message.split(":")[1];
    return run({ ...options, reasoning: "off" }); // degrade to no thinking
  }
  throw error;
}

Prevention

When it happens

Trigger: reasoning != "off" with any model outside the known families: e.g. "claude-3-5-sonnet", "claude-opus-4", a future release like "claude-sonnet-6-20260101" before the regexes are updated, or a custom/renamed deployment id. Alias forms that don't match the exact patterns also fall through.

Common situations: Anthropic ships a new model generation and the framework pin predates it; user config uses an internal alias or dated snapshot not covered by the (?:-\d{8})? patterns; typos like "claude-haiku-45" (missing dash) classify as unknown.

Related errors


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