JuliusBrussee/caveman · error

caveman build: set CAVE_MODEL when zero or multiple…

Error message

caveman build: set CAVE_MODEL when zero or multiple provider credentials exist

What it means

detectedModel() in packages/agent/src/cli.ts maps exactly one present provider credential (ANTHROPIC_API_KEY, OPENAI_API_KEY, or GEMINI/GOOGLE_API_KEY) to a default model. If the filtered list does not contain exactly one entry — zero keys or two or more keys — it throws 'caveman build: set CAVE_MODEL when zero or multiple provider credentials exist' because the default model would be ambiguous.

Solutions

  1. Set CAVE_MODEL to the desired model (e.g. export CAVE_MODEL=anthropic/claude-haiku-4-5) before running caveman build.
  2. Unset the extra provider keys you do not want build to use (e.g. unset OPENAI_API_KEY) so exactly one credential remains.
  3. Export at least one provider key if none is set (e.g. export ANTHROPIC_API_KEY=sk-...).
  4. In CI, add the single intended provider key — or CAVE_MODEL — to the job's environment secrets.
  5. Check `env | grep -E 'ANTHROPIC|OPENAI|GEMINI|GOOGLE_API_KEY'` to see which keys are active in the current shell.

Example fix

# before: two keys exported, ambiguous
export ANTHROPIC_API_KEY=sk-ant-...
export OPENAI_API_KEY=sk-...
caveman build  # throws
# after
export CAVE_MODEL=openai/gpt-5.4-mini
caveman build
Defensive patterns

Strategy: validation

Validate before calling

const keys = ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GEMINI_API_KEY", "GOOGLE_API_KEY"];
const present = keys.filter((k) => process.env[k]);
if (present.length !== 1 && !process.env.CAVE_MODEL) {
  throw new Error(`Set CAVE_MODEL or export exactly one provider key (found: ${present.join(", ") || "none"})`);
}

Try / catch

try {
  await build();
} catch (e) {
  if (e.message.includes("set CAVE_MODEL")) {
    console.error("Exactly one provider credential must be set, or set CAVE_MODEL explicitly.");
  } else throw e;
}

Prevention

When it happens

Trigger: Running 'caveman build' with none of the provider env vars set, or with two or more of ANTHROPIC_API_KEY, OPENAI_API_KEY, GEMINI_API_KEY/GOOGLE_API_KEY exported simultaneously, without CAVE_MODEL set.

Common situations: A shell profile exporting several SDK keys at once (e.g. both Anthropic and OpenAI keys for other tools); CI environment with no credentials at all; recently signed up for a new provider so a second key appeared in the environment.

Understand the failure class

Background: "environment variable is not set" and "Missing keys in environment" errors: what missing required env var messages mean and how to fix them — this error's family across 28 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/c9ce5e008497fde3. Report an issue: GitHub.

Appendix: source

Thrown at packages/agent/src/cli.ts:1371

function localProviderModel(root: string): string | undefined {
  try {
    const parsed = JSON.parse(readFileSync(resolve(root, ".caveman/provider.json"), "utf8")) as { model?: unknown };
    return typeof parsed.model === "string" ? parsed.model : undefined;
  } catch (error) {
    if ((error as NodeJS.ErrnoException).code === "ENOENT") return undefined;
    throw new Error("caveman build: invalid .caveman/provider.json");
  }
}

function detectedModel(): string {
  const configured = [
    process.env.ANTHROPIC_API_KEY && "anthropic/claude-haiku-4-5",
    process.env.OPENAI_API_KEY && "openai/gpt-5.4-mini",
    (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY) && "google/gemini-2.5-flash",
  ].filter((value): value is string => typeof value === "string");
  if (configured.length !== 1) {
    throw new Error("caveman build: set CAVE_MODEL when zero or multiple provider credentials exist");
  }
  return configured[0]!;
}

async function loadAgent(path: string): Promise<AgentDefinition> {
  return agentFromImported(await importFresh(path));
}

function agentFromImported(imported: unknown): AgentDefinition {
  const exported = imported as { default?: AgentDefinition; agent?: AgentDefinition };
  const definition = exported.default ?? exported.agent;
  if (!definition || definition.kind !== "agent") throw new Error("caveman agent: entry must export default agent()");
  return definition;
}

async function readLock(root: string): Promise<CaveBuildLock> {
  return parseCaveBuildLock(JSON.parse(await readFile(resolve(root, ".caveman/agent.lock.json"), "utf8")));
}

View on GitHub (pinned to 3ee70a1026)