JuliusBrussee/caveman · error · Error

Cave Build lock is stale or invalid; refusing Claude launch…

Error message

Cave Build lock is stale or invalid; refusing Claude launch before model spend: ${detail}

What it means

The CLI runs `caveman-agent check` against an existing Cave Build lock before launching Claude. If the checker subprocess fails (non-zero exit or spawn error) the lock is treated as stale or invalid, and launch is refused before any model spend; the checker's stderr or error message is appended as `detail`.

Solutions

  1. Read the appended detail text after the colon — it contains the checker's stderr explaining why the lock was rejected
  2. Delete the stale lock file and re-run so a fresh valid lock is created
  3. Regenerate the lock with the same CLI/caveman-agent version pair that will validate it
  4. Run `caveman-agent check` manually against the lock path to see the full failure output
  5. Ensure the working directory and env the checker sees match the session the lock was created in

Example fix

// before: leftover lock from crashed session
$ rm .cave-build.lock   # or your configured lockPath
$ caveman-cli launch claude
// after: fresh lock created and validated before launch
Defensive patterns

Strategy: validation

Validate before calling

import { readFileSync } from 'fs';
import { execFileSync } from 'child_process';
function lockIsValid(lockPath: string) {
  try { execFileSync('caveman-agent', ['check'], { stdio: 'pipe' }); return true; }
  catch (e) { console.error('lock check failed:', e.stderr?.toString()); return false; }
}

Try / catch

try {
  cli.launchClaude();
} catch (err) {
  if ((err as Error).message.startsWith('Cave Build lock is stale or invalid')) {
    const detail = (err as Error).message.split(': ').slice(1).join(': ');
    console.error('Checker rejected lock:', detail, '— delete the lock and re-run.');
  } else throw err;
}

Prevention

When it happens

Trigger: spawnSync(caveman-agent, ["check"]) returns checked.error or checked.status !== 0 while validating the lock file — e.g. the lock JSON no longer matches what the checker expects, the checker rejected the lock contents, or the checker itself crashed/was not runnable.

Common situations: Lock file written by an older CLI version whose schema the current caveman-agent rejects; a leftover lock from a crashed session; caveman-agent failing its own consistency check because the lock references a missing session; the checker emitting errors on stderr due to environment problems.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/a5ba99d2fca5ecf2. Report an issue: GitHub.

Appendix: source

Thrown at packages/cli/src/index.ts:5562

  try {
    rawBeforeCheck = readFileSync(lockPath, "utf8");
  } catch (error) {
    if ((error as NodeJS.ErrnoException).code === "ENOENT") return {};
    throw error;
  }
  const checker = process.env.CAVEMAN_AGENT_BIN || which("caveman-agent");
  if (!checker) {
    throw new Error("Cave Build lock exists but caveman-agent checker is unavailable; refusing Claude launch before model spend");
  }
  const invocation = portableInvocation(checker, ["check"]);
  const checked = spawnSync(invocation.command, invocation.args, {
    cwd: process.cwd(),
    env: process.env,
    encoding: "utf8",
  });
  if (checked.error || checked.status !== 0) {
    const detail = String(checked.stderr || checked.error?.message || "lock check failed").trim();
    throw new Error(`Cave Build lock is stale or invalid; refusing Claude launch before model spend: ${detail}`);
  }
  const rawAfterCheck = readFileSync(lockPath, "utf8");
  if (rawAfterCheck !== rawBeforeCheck) {
    throw new Error("Cave Build lock changed during validation; refusing Claude launch before model spend");
  }
  const lock = JSON.parse(rawAfterCheck) as { harness?: { id?: unknown } };
  if (lock.harness?.id !== "claude") {
    throw new Error(
      "Cave Build is Pi-specific; refusing to attach its identity to Claude Code execution",
    );
  }
  throw new Error(
    "Claude-specific Cave Build execution is unavailable until model, reasoning, budget, recovery, and wire selectors are enforced",
  );
}

function firstEnvSecret(env: NodeJS.ProcessEnv, keys: string[]): string | undefined {
  for (const key of keys) {

View on GitHub (pinned to 3ee70a1026)