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

  1. Re-select the model/reasoning to exactly match the plan, or regenerate the plan from the current selection.
  2. Normalize reasoning values: map plan reasoning "none" to "off" before comparing.
  3. Ensure the same plan object flows from planning into streamAgentInternal rather than a re-fetched or re-built copy.
  4. 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

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


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)