{"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":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":5571,"sourceCode":"  }\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;\n  if (wrapMode(gw) === \"local\") {","sourceCodeStart":5553,"sourceCodeEnd":5589,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/cli/src/index.ts#L5553-L5589","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\n$ cat .cave-build.lock | jq .harness.id\n\"pi\"\n// after\n$ rm .cave-build.lock\n$ caveman-cli launch claude   # creates lock with harness.id \"claude\"","handlingStrategy":"validation","validationCode":"import { readFileSync } from 'fs';\nfunction lockHarnessMatches(lockPath: string, expected: string) {\n  try {\n    const lock = JSON.parse(readFileSync(lockPath, 'utf8'));\n    return lock?.harness?.id === expected;\n  } catch { return false; }\n}\n// check lockHarnessMatches(lockPath, 'claude') before launching Claude","typeGuard":"function isClaudeLock(lock: unknown): lock is { harness: { id: 'claude' } } {\n  return (lock as any)?.harness?.id === 'claude';\n}","tryCatchPattern":"try {\n  cli.launchClaude();\n} catch (err) {\n  if ((err as Error).message.includes('Cave Build is Pi-specific')) {\n    console.error('Lock belongs to the Pi harness; remove it or launch Pi instead.');\n  } else throw err;\n}","preventionTips":["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"],"tags":["lockfile","harness-identity","cave-build","cli-gate"],"backgroundTag":"conflicting-config-options","analyzedSha":"3ee70a102609e550bd2e68004bf5990a9341c851","analyzedAt":"2026-09-20T15:53:39.229Z","contentChangedAt":"2026-09-20T15:53:39.229Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}