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
- Read the appended detail text after the colon — it contains the checker's stderr explaining why the lock was rejected
- Delete the stale lock file and re-run so a fresh valid lock is created
- Regenerate the lock with the same CLI/caveman-agent version pair that will validate it
- Run `caveman-agent check` manually against the lock path to see the full failure output
- 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
- Delete locks left by crashed sessions instead of retrying launches against them
- Keep the CLI and caveman-agent at matching versions so lock schemas agree
- Run `caveman-agent check` manually to diagnose rejected locks
- Avoid copying lock files between machines or directories
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
- Cave Build is Pi-specific; refusing to attach its identity…
- Cave Build lock changed during validation; refusing Claude…
- Cave Build lock exists but caveman-agent checker is…
- registry output has no valid registry_sha256
- apiKey, baseURL, and agent are required
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)