JuliusBrussee/caveman · error

Cave Build is Pi-specific; refusing to attach its identity t

Error message

Cave Build is Pi-specific; refusing to attach its identity to Claude Code execution

What it means

After the lock validates, its JSON is parsed and harness.id must equal "claude". Cave Build locks carry a harness identity and the feature is currently Pi-specific; attaching a non-claude (e.g. "pi") lock identity to a Claude Code launch is refused to prevent misattributed execution and billing.

Source

Thrown at packages/cli/src/index.ts:5055

    throw new Error("Cave Build lock exists but caveman-agent checker is unavailable; refusing Claude launch before model spend");
  }
  const invocation = portableInvocation(checker, ["check"]);
  const checked = spawnSync(invocation.command, invocation.args, {
    cwd: process.cwd(),
    env: process.env,
    encoding: "utf8",
  });
  if (checked.error || checked.status !== 0) {
    const detail = String(checked.stderr || checked.error?.message || "lock check failed").trim();
    throw new Error(`Cave Build lock is stale or invalid; refusing Claude launch before model spend: ${detail}`);
  }
  const rawAfterCheck = readFileSync(lockPath, "utf8");
  if (rawAfterCheck !== rawBeforeCheck) {
    throw new Error("Cave Build lock changed during validation; refusing Claude launch before model spend");
  }
  const lock = JSON.parse(rawAfterCheck) as { harness?: { id?: unknown } };
  if (lock.harness?.id !== "claude") {
    throw new Error(
      "Cave Build is Pi-specific; refusing to attach its identity to Claude Code execution",
    );
  }
  throw new Error(
    "Claude-specific Cave Build execution is unavailable until model, reasoning, budget, recovery, and wire selectors are enforced",
  );
}

function firstEnvSecret(env: NodeJS.ProcessEnv, keys: string[]): string | undefined {
  for (const key of keys) {
    const value = env[key];
    if (typeof value === "string" && value.trim()) return value.trim();
  }
  return undefined;
}

function maybeWarnHermesMissingKey(agent: AgentProfile | undefined, gw = gatewayURL()) {
  if (agent?.id !== "hermes") return;

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Remove or archive .caveman/agent.lock.json from the Pi session before launching Claude
  2. Use a separate working directory for Claude vs Pi Cave Build sessions
  3. Regenerate the lock with harness.id "claude" if your workflow requires a lock for Claude

Example fix

# before
# .caveman/agent.lock.json: { "harness": { "id": "pi" } }
claude  # Error: Cave Build is Pi-specific

# after
rm .caveman/agent.lock.json
claude
Defensive patterns

Strategy: validation

Validate before calling

import { readFileSync } from "node:fs";
const lock = JSON.parse(readFileSync(".caveman/agent.lock.json", "utf8"));
if (lock.harness?.id !== "claude") {
  throw new Error(`lock harness is ${lock.harness?.id ?? "unset"}; remove the lock before launching Claude`);
}

Type guard

function isClaudeLock(v: unknown): v is { harness: { id: "claude" } } {
  return !!v && typeof v === "object"
    && typeof (v as any).harness === "object" && (v as any).harness !== null
    && (v as any).harness.id === "claude";
}

Try / catch

try {
  claudeCaveBuildEnv();
} catch (error) {
  if (/Pi-specific/.test((error as Error).message)) {
    rmSync(".caveman/agent.lock.json");   // Pi lock does not belong to a Claude launch
    claudeCaveBuildEnv();
  } else throw error;
}

Prevention

When it happens

Trigger: Launching Claude via the caveman launcher in a project whose .caveman/agent.lock.json has harness.id set to anything other than "claude" (typically "pi" from a Cave Build run).

Common situations: Switching between Pi and Claude harnesses in the same project; reusing a project directory that previously ran Cave Build with Pi.

Related errors


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