JuliusBrussee/caveman · error · Error

cave_harness_model_invalid

cave_harness_model_invalid

Error message

cave_harness_model_invalid

What it means

expectedProviderModel requires plan.model to be a 'provider/model' string with a non-empty provider before the first '/' and a non-empty model after it. It throws when the '/' separator is missing, at index 0, or the last character, because the adapter can never split plan.model into the provider/model pair that harness execution and response identity are matched against.

Solutions

  1. Use a plan produced by the framework's compile pipeline, which guarantees the provider/model shape
  2. If building the plan yourself, set plan.model to 'provider/model', e.g. "anthropic/claude-sonnet-4-5"
  3. Add a guard on plan.model before creating/running the adapter (see type guard below)

Example fix

// before
const plan = { model: "claude-sonnet-4-5", /* ... */ };
await adapter.run({ plan, /* ... */ }); // throws cave_harness_model_invalid

// after
const plan = { model: "anthropic/claude-sonnet-4-5", /* ... */ };
await adapter.run({ plan, /* ... */ });
Defensive patterns

Strategy: type-guard

Validate before calling

const separator = plan.model.indexOf("/");
if (separator < 1 || separator === plan.model.length - 1) {
  throw new Error(`plan.model must be 'provider/model', got: ${plan.model}`);
}

Type guard

function isProviderModelString(value: string): value is `${string}/${string}` {
  const separator = value.indexOf("/");
  return separator > 0 && separator < value.length - 1;
}

Try / catch

try {
  await adapter.run(request);
} catch (error) {
  if (error instanceof Error && error.message === "cave_harness_model_invalid") {
    // plan.model lacks the required provider/model shape — rebuild the plan
    // through the compile pipeline or fix the model identifier.
  }
  throw error;
}

Prevention

When it happens

Trigger: Calling createVercelAISDKAdapter or createMastraAdapter (or any createHarnessAdapter run reaching harnessExecution) with a CavePlan whose model is "claude-sonnet-4-5" (no slash), "/claude-sonnet-4-5" (empty provider), or "anthropic/" (empty model).

Common situations: Hand-assembling a CavePlan instead of taking it from the compile/build pipeline; a config layer storing just the model name without the provider prefix; plan serialization code that trims or rewrites the '/'-joined identifier.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@766dce6b13 (2026-08-18). Data as JSON: /api/errors/514de8bb26fa60c0. Report an issue: GitHub.

Appendix: source

Thrown at packages/agent/src/adapters.ts:501

      runtime.modelId.startsWith("dynamic:")) {
    throw new Error("cave_eve_runtime_identity_missing");
  }
  const separator = runtime.modelId.indexOf("/");
  if (separator < 1 || separator === runtime.modelId.length - 1 ||
      typeof runtime.eveVersion !== "string" || runtime.eveVersion.length === 0) {
    throw new Error("cave_eve_runtime_identity_missing");
  }
  return {
    provider: runtime.modelId.slice(0, separator),
    model: runtime.modelId.slice(separator + 1),
    upstreamVersion: runtime.eveVersion,
  };
}

function expectedProviderModel(plan: CavePlan): { provider: string; model: string } {
  const separator = plan.model.indexOf("/");
  if (separator < 1 || separator === plan.model.length - 1) {
    throw new Error("cave_harness_model_invalid");
  }
  return { provider: plan.model.slice(0, separator), model: plan.model.slice(separator + 1) };
}

function normalizedResponseModel(value: unknown, provider: string): string {
  if (typeof value !== "string" || value.length === 0) {
    throw new Error("cave_harness_model_identity_missing");
  }
  return value.startsWith(`${provider}/`) ? value.slice(provider.length + 1) : value;
}

function terminalFinishReason(value: unknown): boolean {
  return typeof value === "string" && ["stop", "length", "content-filter", "other"].includes(value);
}

function record(value: unknown, error: string): Record<string, unknown> {
  if (!isRecord(value)) throw new Error(error);
  return value;

View on GitHub (pinned to 766dce6b13)