{"record":{"id":"34db8fa45ef6ad61","repo":"JuliusBrussee/caveman","slug":"cave-stale-lock-registration","errorCode":null,"errorMessage":"cave_stale_lock:registration","messagePattern":"cave_stale_lock:registration","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/cli.ts","lineNumber":475,"sourceCode":"      available: Boolean(process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY),\n    };\n  }\n  return { name: `credential for ${provider || \"unknown provider\"}`, available: false };\n}\n\nasync function register(_args: string[]): Promise<void> {\n  const root = process.cwd();\n  const controlURL = process.env.CAVE_CONTROL_URL?.replace(/\\/+$/, \"\");\n  const token = process.env.CAVE_TOKEN ?? process.env.CAVE_API_TOKEN;\n  const projectID = process.env.CAVE_PROJECT_ID;\n  if (!controlURL || !token || !projectID) {\n    throw new Error(\"register requires CAVE_CONTROL_URL, CAVE_TOKEN, and CAVE_PROJECT_ID\");\n  }\n  const lock = await readLock(root);\n  const loaded = await loadBuildInputs(root, \"caveman.config.ts\");\n  const checked = await validLockIdentity(root, loaded.config.entry);\n  if (!checked || checked.build_sha256 !== lock.build_sha256) {\n    throw new Error(\"cave_stale_lock:registration\");\n  }\n  const response = await fetch(`${controlURL}/api/v1/projects/${encodeURIComponent(projectID)}/agent-builds`, {\n    method: \"POST\",\n    headers: {\n      authorization: `Bearer ${token}`,\n      \"content-type\": \"application/json\",\n    },\n    body: JSON.stringify({\n      agent_slug: lock.agent_id,\n      build_sha256: lock.build_sha256,\n      plan_sha256: lock.plan_sha256,\n      source_sha256: lock.source_sha256,\n      eval_suite_sha256: lock.eval_suite_sha256,\n      catalog_sha256: lock.catalog_sha256,\n      transform_registry_sha256: lock.runtime.transform_registry_sha256,\n      harness: lock.harness.id,\n      adapter_version: lock.harness.adapter_version,\n      upstream_version: lock.harness.upstream_version,","sourceCodeStart":457,"sourceCodeEnd":493,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/cli.ts#L457-L493","documentation":"Before registering, the CLI re-validates .caveman/agent.lock.json against freshly loaded build inputs: validLockIdentity(root, entry) must succeed AND its build_sha256 must equal the one in the lock read earlier. Failure means the lock is stale relative to current sources/config — registering it would publish a build hash that no longer matches the code, so registration is refused with the cave_stale_lock:registration scope tag.","triggerScenarios":"Editing any build input (entry, caveman.config.ts, eval files, source graph) after `caveman-agent build` and then running register; no lock at all (validLockIdentity falsy); or the lock being regenerated between readLock and validation (sha mismatch).","commonSituations":"Build → commit/push → teammate pulls → sources move ahead of the committed lock; CI checkout where .caveman/agent.lock.json is cached but the repo advanced; local hotfix edit forgotten before register.","solutions":["Rebuild to refresh the lock: caveman-agent build, then caveman-agent register.","If register keeps failing, run caveman-agent doctor to see the lock check state and confirm which inputs drifted.","Treat the lock as an artifact: commit it at the same commit as the sources it hashes, and never edit sources between build and register."],"exampleFix":"# before\ncaveman-agent build\n# (edit src/agent.ts)\ncaveman-agent register # throws cave_stale_lock:registration\n\n# after\ncaveman-agent build\ncaveman-agent register # no edits in between","handlingStrategy":"validation","validationCode":"async function lockMatchesCurrentBuild(root: string): Promise<boolean> {\n  const loaded = await loadBuildInputs(root, \"caveman.config.ts\");\n  const lock = await readLock(root);\n  const checked = await validLockIdentity(root, loaded.config.entry);\n  return Boolean(checked) && checked.build_sha256 === lock.build_sha256;\n}","typeGuard":null,"tryCatchPattern":"try {\n  await register(args);\n} catch (error) {\n  if (error instanceof Error && error.message === \"cave_stale_lock:registration\") {\n    // deterministic fix: rebuild to refresh the lock, then register once\n    await build([\"caveman.config.ts\"]);\n    return register(args);\n  }\n  throw error;\n}","preventionTips":["Run build and register back-to-back in one CI job with no source edits in between.","Commit .caveman/agent.lock.json in the same commit as the sources it hashes.","If register keeps failing after a fresh build, run doctor to identify which build input keeps drifting."],"tags":["registration","lock","build-hash","stale-state"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}