{"record":{"id":"8fbbc8df96c8651f","repo":"JuliusBrussee/caveman","slug":"cave-build-is-pi-specific-refusing-to-attach-its","errorCode":null,"errorMessage":"Cave Build is Pi-specific; refusing to attach its identity to Claude Code execution","messagePattern":"Cave Build is Pi-specific; refusing to attach its identity to Claude Code execution","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":5055,"sourceCode":"    throw new Error(\"Cave Build lock exists but caveman-agent checker is unavailable; refusing Claude launch before model spend\");\n  }\n  const invocation = portableInvocation(checker, [\"check\"]);\n  const checked = spawnSync(invocation.command, invocation.args, {\n    cwd: process.cwd(),\n    env: process.env,\n    encoding: \"utf8\",\n  });\n  if (checked.error || checked.status !== 0) {\n    const detail = String(checked.stderr || checked.error?.message || \"lock check failed\").trim();\n    throw new Error(`Cave Build lock is stale or invalid; refusing Claude launch before model spend: ${detail}`);\n  }\n  const rawAfterCheck = readFileSync(lockPath, \"utf8\");\n  if (rawAfterCheck !== rawBeforeCheck) {\n    throw new Error(\"Cave Build lock changed during validation; refusing Claude launch before model spend\");\n  }\n  const lock = JSON.parse(rawAfterCheck) as { harness?: { id?: unknown } };\n  if (lock.harness?.id !== \"claude\") {\n    throw new Error(\n      \"Cave Build is Pi-specific; refusing to attach its identity to Claude Code execution\",\n    );\n  }\n  throw new Error(\n    \"Claude-specific Cave Build execution is unavailable until model, reasoning, budget, recovery, and wire selectors are enforced\",\n  );\n}\n\nfunction firstEnvSecret(env: NodeJS.ProcessEnv, keys: string[]): string | undefined {\n  for (const key of keys) {\n    const value = env[key];\n    if (typeof value === \"string\" && value.trim()) return value.trim();\n  }\n  return undefined;\n}\n\nfunction maybeWarnHermesMissingKey(agent: AgentProfile | undefined, gw = gatewayURL()) {\n  if (agent?.id !== \"hermes\") return;","sourceCodeStart":5037,"sourceCodeEnd":5073,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/cli/src/index.ts#L5037-L5073","documentation":"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.","triggerScenarios":"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).","commonSituations":"Switching between Pi and Claude harnesses in the same project; reusing a project directory that previously ran Cave Build with Pi.","solutions":["Remove or archive .caveman/agent.lock.json from the Pi session before launching Claude","Use a separate working directory for Claude vs Pi Cave Build sessions","Regenerate the lock with harness.id \"claude\" if your workflow requires a lock for Claude"],"exampleFix":"# before\n# .caveman/agent.lock.json: { \"harness\": { \"id\": \"pi\" } }\nclaude  # Error: Cave Build is Pi-specific\n\n# after\nrm .caveman/agent.lock.json\nclaude","handlingStrategy":"validation","validationCode":"import { readFileSync } from \"node:fs\";\nconst lock = JSON.parse(readFileSync(\".caveman/agent.lock.json\", \"utf8\"));\nif (lock.harness?.id !== \"claude\") {\n  throw new Error(`lock harness is ${lock.harness?.id ?? \"unset\"}; remove the lock before launching Claude`);\n}","typeGuard":"function isClaudeLock(v: unknown): v is { harness: { id: \"claude\" } } {\n  return !!v && typeof v === \"object\"\n    && typeof (v as any).harness === \"object\" && (v as any).harness !== null\n    && (v as any).harness.id === \"claude\";\n}","tryCatchPattern":"try {\n  claudeCaveBuildEnv();\n} catch (error) {\n  if (/Pi-specific/.test((error as Error).message)) {\n    rmSync(\".caveman/agent.lock.json\");   // Pi lock does not belong to a Claude launch\n    claudeCaveBuildEnv();\n  } else throw error;\n}","preventionTips":["Use separate project directories (or clean locks) when alternating Pi and Claude","Check lock.harness.id before launching Claude in shared workspaces","Add .caveman/agent.lock.json to per-machine ignore so it is not cloned around"],"tags":["cave-build","harness-identity","claude","lock"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}