JuliusBrussee/caveman · error · 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 provider/model/reasoning a runtime actually selected is exactly the locked plan's selection, by comparing `${provider}/${model}` to plan.model and reasoning to plan.reasoning (with plan value "none" normalized to runtime value "off"). It is the execution kernel's guard that locked builds never silently run a different model or effort level than the compiler selected. The string comparison means provider and model identity, not just the concatenation, must match byte-for-byte.
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 766dce6b13)
Solutions
- Derive provider and model from plan.model itself (split on "/" once, rejoin the remainder) instead of using an independent config value
- Map reasoning exactly: selected.reasoning = plan.reasoning === "none" ? "off" : plan.reasoning
- If the mismatch is intentional (you want a different model), recompile the plan/build rather than bypassing the check
- Log both `${provider}/${model}` and plan.model next to the two reasoning values to see which half diverged
Example fix
// before
validatePlanSelection(plan, {
provider: config.provider, // e.g. "openai"
model: config.model, // e.g. "gpt-4o" — may differ from plan
reasoning: "low", // hardcoded, plan locked "medium"
});
// after — derive everything from the locked plan
const [provider, ...modelParts] = plan.model.split("/");
validatePlanSelection(plan, {
provider,
model: modelParts.join("/"),
reasoning: plan.reasoning === "none" ? "off" : plan.reasoning,
}); Defensive patterns
Strategy: validation
Validate before calling
import type { CavePlan } from "@caveman-ai/agent";
function selectionFromPlan(plan: CavePlan): {
provider: string;
model: string;
reasoning: "off" | "minimal" | "low" | "medium" | "high";
} {
const [provider, ...modelParts] = plan.model.split("/");
return {
provider,
model: modelParts.join("/"),
reasoning: plan.reasoning === "none" ? "off" : plan.reasoning,
};
}
// construct `selected` exclusively via selectionFromPlan(plan) — never from app config Type guard
const isRuntimeReasoning = (value: unknown): value is "off" | "minimal" | "low" | "medium" | "high" => ["off", "minimal", "low", "medium", "high"].includes(value as string);
Try / catch
try {
validatePlanSelection(plan, selected);
} catch (error) {
if (error instanceof Error && error.message === "cave_execution_plan_selection_mismatch") {
const expectedReasoning = plan.reasoning === "none" ? "off" : plan.reasoning;
throw new Error(
`runtime selected ${selected.provider}/${selected.model}/${selected.reasoning} ` +
`but plan locked ${plan.model}/${expectedReasoning}`,
{ cause: error },
);
}
throw error;
} Prevention
- Always derive provider/model by splitting plan.model, and map reasoning "none" -> "off"
- Never source the selected model from app/user config when executing a locked plan
- Recompile the plan when you want a different selection instead of bypassing the check
- Unit-test the selection mapping whenever the framework's reasoning vocabulary changes
When it happens
Trigger: Passing selected.reasoning "none" (plan vocabulary) instead of "off" (runtime vocabulary); building selected from your app config's default model instead of the plan's model string; a provider or model string containing an extra slash so the concatenation shifts (e.g. model "openrouter/anthropic/claude-..."); validating against a different CavePlan than the one locked.
Common situations: Adapters wiring a user-configurable model alongside a locked build; framework upgrades renaming reasoning levels; copy-pasted selection code that hardcodes reasoning "low" while the plan locked "medium"; splitting plan.model on the wrong delimiter before recombining.
Related errors
- cave_harness_build_mismatch
- cave_harness_agent_mismatch
- cave_harness_plan_mismatch
- cave_harness_plan_digest_mismatch
- cave_harness_context_ir_mismatch
AI-assisted analysis of JuliusBrussee/caveman@766dce6b13 (2026-08-18).
Data as JSON: /api/errors/935675a62240d9a6.
Report an issue: GitHub.