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
- Export all three: CAVE_CONTROL_URL=https://cave.example.com CAVE_TOKEN=… CAVE_PROJECT_ID=… caveman-agent register.
- If your secret is named CAVE_API_TOKEN, that alias is accepted — but CAVE_API_KEY is not; rename or additionally set CAVE_TOKEN.
- 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
- Put all three vars (token may be CAVE_TOKEN or CAVE_API_TOKEN — no other alias) in the register step's env in CI.
- Fail fast with your own presence check in wrapper scripts so the error names what's missing.
- Never print the token value when debugging env — check presence only.
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
- empty version output
- no provider credential detected; pass --provider
- multiple provider credentials detected; pass --provider
- cache-replay: OPENAI_API_KEY unavailable
- cache-replay: ANTHROPIC_API_KEY unavailable
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/e9b6cedbd2c4f1d7.
Report an issue: GitHub.