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

  1. 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`).
  2. Remove competing overrides: unset `ANTHROPIC_MODEL`/`ANTHROPIC_SMALL_FAST_MODEL` and any gateway fallback config so the SDK uses the requested model.
  3. 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.
  4. 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

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


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 it

View on GitHub (pinned to 3ee70a1026)