{"record":{"id":"4457ae48d8337dce","repo":"JuliusBrussee/caveman","slug":"cave-provider-model-identity-mismatch-expected-selected-the","errorCode":"cave_provider_model_identity_mismatch","errorMessage":"expected ${selected}, the SDK answered as ${assistantModel}","messagePattern":"expected (.+?), the SDK answered as (.+?)","errorType":"error_code","errorClass":"CavemanRunError","httpStatus":null,"severity":"error","filePath":"packages/agent/src/claude-runtime.ts","lineNumber":330,"sourceCode":"      });\n    }\n    for (const name of toolCalls) receipt.recordToolCall(name);\n    const carry = (code: string, message: string): CavemanRunError =>\n      new CavemanRunError(code, message, receipt.build({\n        runId: runID,\n        agentId: definition.id,\n        stopReason: \"complete\",\n        meter: undefined,\n      }));\n\n    if (result === undefined || result.subtype !== \"success\" || result.is_error) {\n      throw carry(\n        `cave_claude_terminal_${result?.subtype ?? \"missing\"}`,\n        `the Claude Agent SDK ended with ${result?.subtype ?? \"no result\"}`,\n      );\n    }\n    if (assistantModel !== undefined && normalizeClaudeModel(assistantModel) !== selected) {\n      throw carry(\n        \"cave_provider_model_identity_mismatch\",\n        `expected ${selected}, the SDK answered as ${assistantModel}`,\n      );\n    }\n    const text = result.result;\n    if (definition.output?.schema !== undefined) {\n      let parsed: unknown;\n      try {\n        parsed = result.structured_output ?? JSON.parse(text);\n      } catch {\n        throw carry(\"cave_output_schema_invalid_json\", \"the SDK output was not valid JSON\");\n      }\n      if (!Value.Check(definition.output.schema, parsed)) {\n        throw carry(\"cave_output_schema_mismatch\", \"the SDK output did not match the declared schema\");\n      }\n    }\n    // Past the success gate, usage must be accountable. A success whose usage\n    // validateProviderUsage rejected is a real evidence failure — surface it","sourceCodeStart":312,"sourceCodeEnd":348,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/agent/src/claude-runtime.ts#L312-L348","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nawait runClaudeAgent({ model: \"claude-sonnet-4-5\" /* alias resolved elsewhere to another snapshot */ });\n// after\nawait runClaudeAgent({ model: \"claude-sonnet-4-5-20250929\" }); // exact id the SDK reports","handlingStrategy":"validation","validationCode":"// Verify the exact model id before the run and keep aliases out of the definition.\nconst MODEL = \"claude-sonnet-4-5-20250929\"; // exact provider id, no alias, no prefix\nif (!MODEL.startsWith(\"claude-\")) throw new Error(`use exact claude model id, got ${MODEL}`);","typeGuard":"function isExactModelId(m: string): boolean {\n  return /^claude-[a-z0-9.-]+\\d{8}$/.test(m); // dated snapshot id, no alias\n}","tryCatchPattern":"try {\n  await runClaudeAgent(opts);\n} catch (e) {\n  if (e instanceof CavemanRunError && e.code === \"cave_provider_model_identity_mismatch\") {\n    // e.message names both ids — re-pin `model` to the answered one or disable gateway fallback.\n  }\n  throw e;\n}","preventionTips":["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."],"tags":["provider","model-identity","claude-agent-sdk","evidence"],"backgroundTag":"type-mismatch","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}