JuliusBrussee/caveman · error · CavemanRunError
cave_provider_model_identity_mismatch
cave_provider_model_identity_mismatch
Error message
expected ${selected}, the SDK answered as ${assistantModel} What it means
After the Claude Agent SDK reports a successful result, the executor compares the model the SDK actually answered with (`assistantModel`, captured from assistant messages) against the model the caller selected. The comparison strips the optional `anthropic/` vendor prefix via `normalizeClaudeModel`, so this only fires when the identities genuinely differ. The library throws it because exact provider/model identity is required evidence for every run — usage, cost, and locks are only valid for the model that was actually selected.
Solutions
- Print both ids from the error and set the selected model to the exact id the SDK reports (including date suffix, e.g. `claude-sonnet-4-5-20250929`).
- Remove competing overrides: unset `ANTHROPIC_MODEL`/`ANTHROPIC_SMALL_FAST_MODEL` and any gateway fallback config so the SDK uses the requested model.
- If your gateway substitutes models deliberately, disable fallback or route to an endpoint that preserves the model id, since the run cannot be pinned otherwise.
- Check that the SDK version is the exact-pinned one; an unpinned/older SDK may report model ids in a different format.
Example fix
// before
await runClaudeAgent({ model: "claude-sonnet-4-5" /* alias resolved elsewhere to another snapshot */ });
// after
await runClaudeAgent({ model: "claude-sonnet-4-5-20250929" }); // exact id the SDK reports Defensive patterns
Strategy: validation
Validate before calling
// Verify the exact model id before the run and keep aliases out of the definition.
const MODEL = "claude-sonnet-4-5-20250929"; // exact provider id, no alias, no prefix
if (!MODEL.startsWith("claude-")) throw new Error(`use exact claude model id, got ${MODEL}`); Type guard
function isExactModelId(m: string): boolean {
return /^claude-[a-z0-9.-]+\d{8}$/.test(m); // dated snapshot id, no alias
} Try / catch
try {
await runClaudeAgent(opts);
} catch (e) {
if (e instanceof CavemanRunError && e.code === "cave_provider_model_identity_mismatch") {
// e.message names both ids — re-pin `model` to the answered one or disable gateway fallback.
}
throw e;
} Prevention
- Always configure dated snapshot model ids, never aliases like `claude-sonnet-4-5`.
- Unset ANTHROPIC_MODEL / ANTHROPIC_SMALL_FAST_MODEL so env cannot override the selected model.
- Disable model fallback in any gateway/proxy between you and Anthropic.
- Run `caveman doctor` to confirm the exact-pinned SDK and route before spend.
When it happens
Trigger: Calling `runClaudeAgent`/`runClaudeAgentInternal` with a specific `model` selected, while the SDK's assistant messages report a different `message.model` — e.g. the SDK/gateway substituted a fallback model, an alias resolved to a different snapshot (`claude-sonnet-4` vs a dated snapshot), or routing (bedrock/vertex/gateway) rewrote the model id so normalization no longer matches.
Common situations: Provider-side model fallback or deprecation silently swapping in another model; configuring `ANTHROPIC_MODEL`/small-model env vars that override the selected model; using an alias like `claude-sonnet-4-5` where the account is mapped to a different variant; running through a proxy that renames the model field.
Understand the failure class
Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.
Related errors
- cave_claude_terminal_missing
- cave_harness_incomplete_evidence
- cave_incomplete_evidence
- cave_claude_provider_unsupported
- cave_eve_runtime_identity_missing
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/4457ae48d8337dce.
Report an issue: GitHub.
Appendix: source
Thrown at packages/agent/src/claude-runtime.ts:330
});
}
for (const name of toolCalls) receipt.recordToolCall(name);
const carry = (code: string, message: string): CavemanRunError =>
new CavemanRunError(code, message, receipt.build({
runId: runID,
agentId: definition.id,
stopReason: "complete",
meter: undefined,
}));
if (result === undefined || result.subtype !== "success" || result.is_error) {
throw carry(
`cave_claude_terminal_${result?.subtype ?? "missing"}`,
`the Claude Agent SDK ended with ${result?.subtype ?? "no result"}`,
);
}
if (assistantModel !== undefined && normalizeClaudeModel(assistantModel) !== selected) {
throw carry(
"cave_provider_model_identity_mismatch",
`expected ${selected}, the SDK answered as ${assistantModel}`,
);
}
const text = result.result;
if (definition.output?.schema !== undefined) {
let parsed: unknown;
try {
parsed = result.structured_output ?? JSON.parse(text);
} catch {
throw carry("cave_output_schema_invalid_json", "the SDK output was not valid JSON");
}
if (!Value.Check(definition.output.schema, parsed)) {
throw carry("cave_output_schema_mismatch", "the SDK output did not match the declared schema");
}
}
// Past the success gate, usage must be accountable. A success whose usage
// validateProviderUsage rejected is a real evidence failure — surface itView on GitHub (pinned to 3ee70a1026)