{"record":{"id":"a5ba99d2fca5ecf2","repo":"JuliusBrussee/caveman","slug":"cave-build-lock-is-stale-or-invalid-refusing-clau","errorCode":null,"errorMessage":"Cave Build lock is stale or invalid; refusing Claude launch before model spend: ${detail}","messagePattern":"Cave Build lock is stale or invalid; refusing Claude launch before model spend: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":5047,"sourceCode":"  try {\n    rawBeforeCheck = readFileSync(lockPath, \"utf8\");\n  } catch (error) {\n    if ((error as NodeJS.ErrnoException).code === \"ENOENT\") return {};\n    throw error;\n  }\n  const checker = process.env.CAVEMAN_AGENT_BIN || which(\"caveman-agent\");\n  if (!checker) {\n    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) {","sourceCodeStart":5029,"sourceCodeEnd":5065,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/cli/src/index.ts#L5029-L5065","documentation":"The caveman-agent `check` subprocess spawned to validate the Cave Build lock either failed to spawn (checked.error) or exited non-zero (checked.status !== 0). The lock is treated as stale or invalid and Claude launch is refused before any model spend; the lock checker's stderr (or spawn error message) is embedded as detail.","triggerScenarios":"Spawning `caveman-agent check` in a project with .caveman/agent.lock.json when the lock file references a dead session/unknown lock id, the checker binary is a version that rejects the lock format, or the process itself cannot execute (exec format error, timeout).","commonSituations":"Lock left behind by a crashed Cave Build session; caveman-agent version older/newer than the lock schema; corrupted lock JSON; architecture-mismatched binary.","solutions":["Read the embedded detail — it is the checker's own diagnostic of why the lock is invalid","Update caveman-agent to the matching version via `caveman setup --install`","If the lock is genuinely stale (no live Cave Build session), delete .caveman/agent.lock.json","Verify the binary executes: `caveman-agent check` in the project directory"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"import { spawnSync } from \"node:child_process\";\n// validate the lock the same way before launching\nconst check = spawnSync(\"caveman-agent\", [\"check\"], { cwd: process.cwd(), encoding: \"utf8\" });\nif (check.error || check.status !== 0) {\n  throw new Error(`lock invalid: ${String(check.stderr).trim()}; remove .caveman/agent.lock.json if stale`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  claudeCaveBuildEnv();\n} catch (error) {\n  const msg = (error as Error).message;\n  if (/lock is stale or invalid/.test(msg)) {\n    const detail = msg.split(\": \").pop()!;\n    if (/no active session|unknown lock/i.test(detail)) rmSync(\".caveman/agent.lock.json\");\n    else throw error;  // version/schema problems need caveman-agent updated, not deletion\n  } else throw error;\n}","preventionTips":["Keep caveman-agent and the CLI at matching versions so lock schemas agree","Have CI remove leftover lock files from crashed sessions before launch","Read the embedded detail — it distinguishes stale lock from checker failure"],"tags":["cave-build","lock","stale","spend-guard"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}