JuliusBrussee/caveman · error · Error
cave_claude_provider_unsupported
Error message
cave_claude_provider_unsupported
What it means
resolveClaudeModel() resolved a model string but its provider prefix is not "anthropic" (or there is no "provider/model" separator at position >= 1). The Claude harness only speaks the Anthropic provider, so models like "openai/gpt-4o" or malformed strings like "/claude-haiku" are rejected before the SDK call.
Source
Thrown at packages/agent/src/claude-runtime.ts:471
const target = name.toLowerCase();
const kept = (raw ?? "").split(/\r\n|\n|\r/).filter((line) => {
if (!line.trim()) return false;
const colon = line.indexOf(":");
return (colon < 0 ? line : line.slice(0, colon)).trim().toLowerCase() !== target;
});
kept.push(`${name}: ${value}`);
return kept.join("\n");
}
function resolveClaudeModel(definition: AgentDefinition, rootDir: string): string {
const configured = typeof definition.model === "string"
? definition.model
: process.env.CAVE_MODEL ?? localModel(rootDir) ??
(process.env.ANTHROPIC_API_KEY ? "anthropic/claude-haiku-4-5" : undefined);
if (configured === undefined) throw new Error("cave_claude_model_required");
const separator = configured.indexOf("/");
if (separator < 1 || configured.slice(0, separator) !== "anthropic") {
throw new Error("cave_claude_provider_unsupported");
}
return normalizeClaudeModel(configured.slice(separator + 1));
}
function localModel(rootDir: string): string | undefined {
try {
const parsed = JSON.parse(readFileSync(resolve(rootDir, ".caveman/provider.json"), "utf8")) as {
model?: unknown;
};
return typeof parsed.model === "string" ? parsed.model : undefined;
} catch (error) {
if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined;
throw new Error("caveman agent: invalid .caveman/provider.json");
}
}
function normalizeClaudeModel(model: string): string {
return model.startsWith("anthropic/") ? model.slice("anthropic/".length) : model;View on GitHub (pinned to 27d5a3981a)
Solutions
- Rewrite the model id with the anthropic prefix: "anthropic/claude-haiku-4-5".
- If you actually want a non-Anthropic model, select the harness/adapter that supports that provider instead of the Claude one.
- Add a config-time lint that model strings for the Claude lane match /^anthropic\/.+/.
Example fix
// before
const definition = { id: "x", model: "openai/gpt-4o-mini" };
// after
const definition = { id: "x", model: "anthropic/claude-haiku-4-5" }; Defensive patterns
Strategy: type-guard
Type guard
function isAnthropicModelRoute(model: string): boolean {
const sep = model.indexOf("/");
return sep >= 1 && model.slice(0, sep) === "anthropic";
} Prevention
- Use provider-prefixed ids ("anthropic/<model>") everywhere; bare ids are rejected on the Claude lane.
- Add a config lint asserting /^anthropic\/.+/ for every model routed to the Claude harness.
- When migrating configs between harnesses, remember model vocabularies differ — rewrite model ids, don't copy them.
When it happens
Trigger: definition.model / CAVE_MODEL / provider.json model set to a non-Anthropic route ("openai/...", "google/...", "groq/..."), a bare model with no slash ("claude-haiku-4-5"), or a leading slash ("/claude-haiku-4-5" makes separator index 0 < 1).
Common situations: Copying a model string from another harness's config (the framework supports pi/vercel-ai-sdk lanes that use openrouter-style ids); switching a project from an OpenAI provider to the Claude harness without rewriting the model id; typo like "anthropic:" instead of "anthropic/".
Related errors
- cave_claude_model_required
- cave_vercel_terminal_failure
- cave_breaker_threshold_invalid
- cave_breaker_retry_requires_budget
- cave_breaker_retry_spend_invalid
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/dcae21f662ee14dd.
Report an issue: GitHub.