JuliusBrussee/caveman · error · Error

register requires CAVE_CONTROL_URL, CAVE_TOKEN, and CAVE_PRO

Error message

register requires CAVE_CONTROL_URL, CAVE_TOKEN, and CAVE_PROJECT_ID

What it means

The register subcommand uploads a validated build to a Cave control plane and requires three env vars: CAVE_CONTROL_URL (the server base URL), CAVE_TOKEN or CAVE_API_TOKEN (bearer credential), and CAVE_PROJECT_ID (target project). If any is missing or CAVE_CONTROL_URL is empty/whitespace-only, registration refuses to start — it will not guess an endpoint or register anonymously.

Source

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

  if (provider === "openai") {
    return { name: "OPENAI_API_KEY", available: Boolean(process.env.OPENAI_API_KEY) };
  }
  if (provider === "google" || provider === "gemini") {
    return {
      name: "GEMINI_API_KEY or GOOGLE_API_KEY",
      available: Boolean(process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY),
    };
  }
  return { name: `credential for ${provider || "unknown provider"}`, available: false };
}

async function register(_args: string[]): Promise<void> {
  const root = process.cwd();
  const controlURL = process.env.CAVE_CONTROL_URL?.replace(/\/+$/, "");
  const token = process.env.CAVE_TOKEN ?? process.env.CAVE_API_TOKEN;
  const projectID = process.env.CAVE_PROJECT_ID;
  if (!controlURL || !token || !projectID) {
    throw new Error("register requires CAVE_CONTROL_URL, CAVE_TOKEN, and CAVE_PROJECT_ID");
  }
  const lock = await readLock(root);
  const loaded = await loadBuildInputs(root, "caveman.config.ts");
  const checked = await validLockIdentity(root, loaded.config.entry);
  if (!checked || checked.build_sha256 !== lock.build_sha256) {
    throw new Error("cave_stale_lock:registration");
  }
  const response = await fetch(`${controlURL}/api/v1/projects/${encodeURIComponent(projectID)}/agent-builds`, {
    method: "POST",
    headers: {
      authorization: `Bearer ${token}`,
      "content-type": "application/json",
    },
    body: JSON.stringify({
      agent_slug: lock.agent_id,
      build_sha256: lock.build_sha256,
      plan_sha256: lock.plan_sha256,
      source_sha256: lock.source_sha256,

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Export all three: CAVE_CONTROL_URL=https://cave.example.com CAVE_TOKEN=… CAVE_PROJECT_ID=… caveman-agent register.
  2. If your secret is named CAVE_API_TOKEN, that alias is accepted — but CAVE_API_KEY is not; rename or additionally set CAVE_TOKEN.
  3. In CI, verify the three secrets are mapped as env vars on the register step (print a boolean presence check, never the values).

Example fix

# before
export CAVE_CONTROL_URL=https://cave.example.com
caveman-agent register # throws: token/project missing

# after
export CAVE_CONTROL_URL=https://cave.example.com
export CAVE_TOKEN=$CAVE_REGISTER_TOKEN
export CAVE_PROJECT_ID=acme-core
caveman-agent register
Defensive patterns

Strategy: validation

Validate before calling

function registerEnvReady(): boolean {
  const url = process.env.CAVE_CONTROL_URL?.replace(/\/+$/, "");
  const token = process.env.CAVE_TOKEN ?? process.env.CAVE_API_TOKEN;
  return Boolean(url && token && process.env.CAVE_PROJECT_ID);
}
if (!registerEnvReady()) {
  throw new Error("set CAVE_CONTROL_URL, CAVE_TOKEN (or CAVE_API_TOKEN), and CAVE_PROJECT_ID");
}

Type guard

interface RegisterEnv { controlUrl: string; token: string; projectId: string }
function parseRegisterEnv(env: NodeJS.ProcessEnv): RegisterEnv | null {
  const controlUrl = env.CAVE_CONTROL_URL?.replace(/\/+$/, "");
  const token = env.CAVE_TOKEN ?? env.CAVE_API_TOKEN;
  const projectId = env.CAVE_PROJECT_ID;
  return controlUrl && token && projectId ? { controlUrl, token, projectId } : null;
}

Prevention

When it happens

Trigger: Running `caveman-agent register` with one or more of CAVE_CONTROL_URL, CAVE_TOKEN/CAVE_API_TOKEN, CAVE_PROJECT_ID unset or set to an empty string. Note CAVE_CONTROL_URL has trailing slashes stripped, so "/"-only values also collapse to falsy.

Common situations: CI secret not mapped into the job env; local shell missing the exported vars (defined in a different terminal or direnv not loaded); token present under a differently-named var (CAVE_API_KEY instead of the accepted names).

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/e9b6cedbd2c4f1d7. Report an issue: GitHub.