{"record":{"id":"e98cfd76466a8616","repo":"JuliusBrussee/caveman","slug":"cave-provider-model-identity-mismatch","errorCode":"cave_provider_model_identity_mismatch","errorMessage":"cave_provider_model_identity_mismatch","messagePattern":"cave_provider_model_identity_mismatch","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/execution-kernel.ts","lineNumber":135,"sourceCode":"  priced: boolean;\n  catalogCostUsd: number;\n}\n\nexport function validateProviderUsage(\n  evidence: ProviderUsageEvidence,\n  options: {\n    expected?: { provider: string; model: string };\n    reportedCostUsd?: number;\n    requirePriced?: boolean;\n  } = {},\n): ValidatedProviderUsage {\n  if (typeof evidence.provider !== \"string\" || evidence.provider.length === 0 ||\n      typeof evidence.model !== \"string\" || evidence.model.length === 0) {\n    throw new Error(\"cave_provider_identity_missing\");\n  }\n  if (options.expected !== undefined &&\n      (evidence.provider !== options.expected.provider || evidence.model !== options.expected.model)) {\n    throw new Error(\"cave_provider_model_identity_mismatch\");\n  }\n  const values = [\n    evidence.inputTokens,\n    evidence.outputTokens,\n    evidence.cacheReadTokens,\n    evidence.cacheWriteTokens,\n    evidence.reasoningTokens,\n    evidence.totalTokens,\n  ];\n  const disjointTotal = evidence.inputTokens + evidence.outputTokens +\n    evidence.cacheReadTokens + evidence.cacheWriteTokens;\n  if (values.some((value) => !Number.isSafeInteger(value) || value < 0) ||\n      evidence.totalTokens <= 0 || evidence.totalTokens !== disjointTotal ||\n      evidence.reasoningTokens > evidence.outputTokens) {\n    throw new Error(\"cave_provider_usage_incomplete\");\n  }\n  const priced = catalogCost({\n    provider: evidence.provider,","sourceCodeStart":117,"sourceCodeEnd":153,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/execution-kernel.ts#L117-L153","documentation":"Thrown by validateProviderUsage (packages/agent/src/execution-kernel.ts:135): the evidence's provider/model pair does not match the expected pair passed in options.expected. The framework enforces that the model that actually answered is exactly the model that was selected in the plan — no silent substitution by the provider.","triggerScenarios":"Calling validateProviderUsage with options.expected set (as locked runs and reserved turns do) while the reported evidence names a different provider or model string (e.g. expected 'anthropic/claude-sonnet-4-5' but the API reported 'claude-sonnet-4-20250514' or a different deployment).","commonSituations":"Provider aliasing: requesting a model alias and receiving the concrete dated model id back; a gateway/proxy rewriting model names; stale plan/lock expecting an old model id after a provider migration; mismatched casing or whitespace in model strings.","solutions":["Compare evidence.model with expected.model strings to see the exact substitution; if the provider legitimately returns a concrete id for your alias, configure the plan with the exact id the provider reports.","Regenerate the lock/plan after changing the model so expected identity matches what the provider reports.","If a proxy rewrites model names, make it pass the original model string through, or align the configured model with what arrives."],"exampleFix":"// before\nvalidateProviderUsage(evidence, { expected: { provider: \"anthropic\", model: \"claude-sonnet-4-5\" } });\n// evidence.model === \"claude-sonnet-4-20250514\"\n\n// after\n// configure the plan with the concrete id the provider reports\nagent({ model: \"anthropic/claude-sonnet-4-20250514\", /* ... */ });","handlingStrategy":"validation","validationCode":"function identityMatches(evidence: { provider: string; model: string }, expected: { provider: string; model: string }): boolean {\n  return evidence.provider === expected.provider && evidence.model === expected.model;\n}\nif (expected && !identityMatches(evidence, expected)) {\n  console.error(`model substitution: expected ${expected.model}, got ${evidence.model}`);\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Configure plans with the exact concrete model id the provider reports, not aliases.","Log evidence.model on every reserved turn to catch substitution early.","After provider model migrations, regenerate locks so expected identity matches."],"tags":["usage","model-identity","provider","validation"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}