{"record":{"id":"935675a62240d9a6","repo":"JuliusBrussee/caveman","slug":"cave-execution-plan-selection-mismatch","errorCode":"cave_execution_plan_selection_mismatch","errorMessage":"cave_execution_plan_selection_mismatch","messagePattern":"cave_execution_plan_selection_mismatch","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/agent/src/execution-kernel.ts","lineNumber":59,"sourceCode":"  readonly plan: CavePlan;\n  readonly planSHA256: string;\n  readonly contextIRSHA256: string;\n  readonly provider: string;\n  readonly model: string;\n}\n\nexport function validatePlanSelection(\n  plan: CavePlan,\n  selected: {\n    provider: string;\n    model: string;\n    reasoning: \"off\" | \"minimal\" | \"low\" | \"medium\" | \"high\";\n  },\n): void {\n  const expectedReasoning = plan.reasoning === \"none\" ? \"off\" : plan.reasoning;\n  if (`${selected.provider}/${selected.model}` !== plan.model ||\n      selected.reasoning !== expectedReasoning) {\n    throw new Error(\"cave_execution_plan_selection_mismatch\");\n  }\n}\n\nexport function prepareLockedHarnessExecution(input: {\n  build: CaveBuildLock;\n  harness: string;\n  adapterVersion: string;\n  upstreamVersion: string;\n  agentId?: string;\n  contextIR: ContextIR;\n  plan: CavePlan;\n}): LockedHarnessPreparation {\n  const build = parseCaveBuildLock(input.build);\n  if (build.harness.id !== input.harness ||\n      build.harness.adapter_version !== input.adapterVersion ||\n      build.harness.upstream_version !== input.upstreamVersion) {\n    throw new Error(\"cave_harness_build_mismatch\");\n  }","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/agent/src/execution-kernel.ts#L41-L77","documentation":"validatePlanSelection asserts that the model/reasoning actually selected at runtime matches the locked CavePlan. The library throws this when the plan was chosen up front but the streaming request deviates from it. It exists to guarantee deterministic, plan-bound execution in the execution kernel.","triggerScenarios":"Calling streamAgentInternal with a selected model string `${provider}/${model}` that differs from plan.model, or a selected.reasoning that differs from the plan's reasoning after mapping plan reasoning \"none\" to \"off\".","commonSituations":"Reasoning effort changed between plan creation and execution; provider/model default applied when plan specified none; a manually constructed selection object passed to streamAgentInternal without regenerating the plan; model alias resolving differently at runtime.","solutions":["Re-select the model/reasoning to exactly match the plan, or regenerate the plan from the current selection.","Normalize reasoning values: map plan reasoning \"none\" to \"off\" before comparing.","Ensure the same plan object flows from planning into streamAgentInternal rather than a re-fetched or re-built copy.","Check the model string is formatted as \"provider/model\" with a single slash-separated provider prefix."],"exampleFix":"// before\nconst selected = { provider: \"anthropic\", model: \"claude-sonnet-4\", reasoning: \"low\" };\nawait streamAgentInternal({ plan, selected });\n// after\nconst selected = { provider: \"anthropic\", model: \"claude-sonnet-4\", reasoning: plan.reasoning === \"none\" ? \"off\" : plan.reasoning };\nawait streamAgentInternal({ plan, selected });","handlingStrategy":"validation","validationCode":"const expectedReasoning = plan.reasoning === \"none\" ? \"off\" : plan.reasoning;\nif (`${selected.provider}/${selected.model}` !== plan.model || selected.reasoning !== expectedReasoning) {\n  throw new Error(`selection drift: plan=${plan.model}/${expectedReasoning} selected=${selected.provider}/${selected.model}/${selected.reasoning}`);\n}\nstreamAgentInternal({ plan, selected });","typeGuard":"function matchesPlan(plan: { model: string; reasoning: string }, selected: { provider: string; model: string; reasoning: string }): boolean {\n  const expectedReasoning = plan.reasoning === \"none\" ? \"off\" : plan.reasoning;\n  return `${selected.provider}/${selected.model}` === plan.model && selected.reasoning === expectedReasoning;\n}","tryCatchPattern":"try {\n  validatePlanSelection({ plan, selected });\n  await streamAgentInternal({ plan, selected });\n} catch (err) {\n  if (err instanceof Error && err.message === \"cave_execution_plan_selection_mismatch\") {\n    const realigned = { provider: plan.model.split(\"/\")[0], model: plan.model.split(\"/\").slice(1).join(\"/\"), reasoning: plan.reasoning === \"none\" ? \"off\" : plan.reasoning };\n    return streamAgentInternal({ plan, selected: realigned });\n  }\n  throw err;\n}","preventionTips":["Derive the runtime selection from the plan object instead of building it independently.","Treat plan objects as immutable after creation.","Map reasoning \"none\"/\"off\" consistently in one shared helper.","Log plan and selection side by side when debugging streaming requests."],"tags":["execution-kernel","invariant","plan","validation"],"backgroundTag":"internal-invariant-violation","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"}