JuliusBrussee/caveman · error
cave_execution_plan_selection_mismatch
cave_execution_plan_selection_mismatch
Error message
cave_execution_plan_selection_mismatch
What it means
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.
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.
Example fix
// before
const selected = { provider: "anthropic", model: "claude-sonnet-4", reasoning: "low" };
await streamAgentInternal({ plan, selected });
// after
const selected = { provider: "anthropic", model: "claude-sonnet-4", reasoning: plan.reasoning === "none" ? "off" : plan.reasoning };
await streamAgentInternal({ plan, selected }); Defensive patterns
Strategy: validation
Validate before calling
const expectedReasoning = plan.reasoning === "none" ? "off" : plan.reasoning;
if (`${selected.provider}/${selected.model}` !== plan.model || selected.reasoning !== expectedReasoning) {
throw new Error(`selection drift: plan=${plan.model}/${expectedReasoning} selected=${selected.provider}/${selected.model}/${selected.reasoning}`);
}
streamAgentInternal({ plan, selected }); Type guard
function matchesPlan(plan: { model: string; reasoning: string }, selected: { provider: string; model: string; reasoning: string }): boolean {
const expectedReasoning = plan.reasoning === "none" ? "off" : plan.reasoning;
return `${selected.provider}/${selected.model}` === plan.model && selected.reasoning === expectedReasoning;
} Try / catch
try {
validatePlanSelection({ plan, selected });
await streamAgentInternal({ plan, selected });
} catch (err) {
if (err instanceof Error && err.message === "cave_execution_plan_selection_mismatch") {
const realigned = { provider: plan.model.split("/")[0], model: plan.model.split("/").slice(1).join("/"), reasoning: plan.reasoning === "none" ? "off" : plan.reasoning };
return streamAgentInternal({ plan, selected: realigned });
}
throw err;
} Prevention
- 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.
When it happens
Trigger: 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".
Common situations: 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.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- cave_harness_model_invalid
- cave_harness_model_invalid
- cave_harness_plan_digest_mismatch
- cave_harness_plan_mismatch
- cave_provider_identity_missing
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/935675a62240d9a6.
Report an issue: GitHub.
Appendix: source
Thrown at packages/agent/src/execution-kernel.ts:59
readonly plan: CavePlan;
readonly planSHA256: string;
readonly contextIRSHA256: string;
readonly provider: string;
readonly model: string;
}
export function validatePlanSelection(
plan: CavePlan,
selected: {
provider: string;
model: string;
reasoning: "off" | "minimal" | "low" | "medium" | "high";
},
): void {
const expectedReasoning = plan.reasoning === "none" ? "off" : plan.reasoning;
if (`${selected.provider}/${selected.model}` !== plan.model ||
selected.reasoning !== expectedReasoning) {
throw new Error("cave_execution_plan_selection_mismatch");
}
}
export function prepareLockedHarnessExecution(input: {
build: CaveBuildLock;
harness: string;
adapterVersion: string;
upstreamVersion: string;
agentId?: string;
contextIR: ContextIR;
plan: CavePlan;
}): LockedHarnessPreparation {
const build = parseCaveBuildLock(input.build);
if (build.harness.id !== input.harness ||
build.harness.adapter_version !== input.adapterVersion ||
build.harness.upstream_version !== input.upstreamVersion) {
throw new Error("cave_harness_build_mismatch");
}View on GitHub (pinned to 3ee70a1026)