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
- Remove the Pi lock file so the Claude launch path is not gated by it
- Create a fresh lock from within the Claude launch flow so harness.id is "claude"
- Use the Pi harness if you actually intend to continue the Cave Build session recorded in the lock
- 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
- Inspect lock harness.id before switching harnesses in the same directory
- Use distinct working directories per harness
- Delete stale locks when switching from Pi to Claude workflows
- Never copy Pi session locks into Claude launch directories
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
- Cave Build lock exists but caveman-agent checker is…
- Cave Build lock changed during validation; refusing Claude…
- Cave Build lock is stale or invalid; refusing Claude launch…
- Claude-specific Cave Build execution is unavailable until…
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)