JuliusBrussee/caveman · error · Error

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

Error message

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

What it means

After validating the Cave Build lock, the CLI checks that the lock's harness id matches the harness being launched. A lock whose harness.id is not "claude" is a Pi-specific Cave Build identity, and attaching it to Claude Code execution is refused.

Solutions

  1. Remove the Pi lock file so the Claude launch path is not gated by it
  2. Create a fresh lock from within the Claude launch flow so harness.id is "claude"
  3. Use the Pi harness if you actually intend to continue the Cave Build session recorded in the lock
  4. Run launches for different harnesses in separate working directories so their locks don't collide

Example fix

// before
$ cat .cave-build.lock | jq .harness.id
"pi"
// after
$ rm .cave-build.lock
$ caveman-cli launch claude   # creates lock with harness.id "claude"
Defensive patterns

Strategy: validation

Validate before calling

import { readFileSync } from 'fs';
function lockHarnessMatches(lockPath: string, expected: string) {
  try {
    const lock = JSON.parse(readFileSync(lockPath, 'utf8'));
    return lock?.harness?.id === expected;
  } catch { return false; }
}
// check lockHarnessMatches(lockPath, 'claude') before launching Claude

Type guard

function isClaudeLock(lock: unknown): lock is { harness: { id: 'claude' } } {
  return (lock as any)?.harness?.id === 'claude';
}

Try / catch

try {
  cli.launchClaude();
} catch (err) {
  if ((err as Error).message.includes('Cave Build is Pi-specific')) {
    console.error('Lock belongs to the Pi harness; remove it or launch Pi instead.');
  } else throw err;
}

Prevention

When it happens

Trigger: A Cave Build lock file exists with lock.harness.id set to something other than "claude" (typically "pi") at the moment a Claude Code launch gate validates it — i.e. the lock was created for/by the Pi harness and is now being used to authorize a Claude launch.

Common situations: A lock left behind by a Pi session in the same directory; switching harnesses without clearing the previous lock; copy/rsync of a project directory carrying a Pi lock into a Claude workflow.

Understand the failure class

Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.

Related errors


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

Appendix: source

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

  }
  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;
  if (wrapMode(gw) === "local") {

View on GitHub (pinned to 3ee70a1026)