{"record":{"id":"b5bf7d48a28c0f2a","repo":"JuliusBrussee/caveman","slug":"cave-build-lock-exists-but-caveman-agent-checker-i","errorCode":null,"errorMessage":"Cave Build lock exists but caveman-agent checker is unavailable; refusing Claude launch before model spend","messagePattern":"Cave Build lock exists but caveman-agent checker is unavailable; refusing Claude launch before model spend","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":5552,"sourceCode":"    stopProxyKeepalive();\n    cleanupWrapTempDirs();\n    removeProxySessionMarker(sessionMarker);\n  }\n  return { code, proxyStarted, routeBypass, sessionStart, summaryKind };\n}\n\nexport function claudeCaveBuildEnv(): NodeJS.ProcessEnv {\n  const lockPath = join(process.cwd(), \".caveman\", \"agent.lock.json\");\n  let rawBeforeCheck: string;\n  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(","sourceCodeStart":5534,"sourceCodeEnd":5570,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/3ee70a102609e550bd2e68004bf5990a9341c851/packages/cli/src/index.ts#L5534-L5570","documentation":"Before launching Claude Code, this CLI validates a Cave Build lock file by running an external `caveman-agent check` command. If the lock file exists but neither CAVEMAN_AGENT_BIN nor a `caveman-agent` on PATH can be found, the launcher refuses to start Claude rather than spending model tokens with an unvalidated lock.","triggerScenarios":"A Cave Build lock file exists in the working context, but `process.env.CAVEMAN_AGENT_BIN` is unset and `which(\"caveman-agent\")` finds nothing — i.e. the checker binary is not installed or not on PATH at the moment the Claude launch gate runs.","commonSituations":"caveman-agent installed in a non-PATH directory (or only in another project's node_modules); running Claude via a wrapper that scrubs PATH; the CLI was upgraded but the companion caveman-agent tool was never installed; CAVEMAN_AGENT_BIN typoed or pointing at a removed file.","solutions":["Install caveman-agent and ensure it is on PATH (`which caveman-agent` resolves)","Set CAVEMAN_AGENT_BIN to the absolute path of the caveman-agent executable","Remove the stale Cave Build lock file if you do not intend to use Cave Build, so the gate is skipped","Re-sync versions between the CLI and caveman-agent after an upgrade","Verify PATH inside the wrapper/CI environment you actually launch Claude from"],"exampleFix":"// before\nexport PATH=/usr/local/bin:/usr/bin\n// after\nexport CAVEMAN_AGENT_BIN=/Users/me/tools/caveman-agent/bin/caveman-agent\nexport PATH=$CAVEMAN_AGENT_BIN_DIR:$PATH","handlingStrategy":"validation","validationCode":"import { existsSync } from 'fs';\nimport { execFileSync } from 'child_process';\nconst checker = process.env.CAVEMAN_AGENT_BIN || 'caveman-agent';\nif (existsSync(lockPath) && !process.env.CAVEMAN_AGENT_BIN) {\n  try { execFileSync('which', ['caveman-agent'], { stdio: 'ignore' }); }\n  catch { throw new Error('caveman-agent not on PATH; set CAVEMAN_AGENT_BIN'); }\n}","typeGuard":null,"tryCatchPattern":"try {\n  cli.launchClaude();\n} catch (err) {\n  if ((err as Error).message.includes('caveman-agent checker is unavailable')) {\n    console.error('Install caveman-agent or set CAVEMAN_AGENT_BIN to its absolute path.');\n  } else throw err;\n}","preventionTips":["Always install the caveman-agent companion tool alongside the CLI","Export CAVEMAN_AGENT_BIN with an absolute path in CI and wrapper scripts","Remove leftover Cave Build locks if you are not using Cave Build","Keep CLI and caveman-agent versions in sync after upgrades"],"tags":["lockfile","cli-gate","missing-binary","cave-build"],"backgroundTag":"missing-dependency","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"}