JuliusBrussee/caveman · error
MCP config change already running for
Error message
MCP config change already running for ${canonicalPath} What it means
When the MCP config lock owner is verified live (its PID answers a kill(0) probe) or its state cannot be proven stale (e.g. the owner file is unreadable/malformed and treated as live), a new config change is refused. Stale locks are reclaimed only after a verified-ESRCH probe.
Solutions
- Wait for the running caveman MCP command to complete and retry.
- Identify the PID in the owner file and terminate it if it is stuck, then retry.
- Fix read permissions on the lock/owner files so liveness can be evaluated.
- If the lock is genuinely orphaned and no process matches, remove lock and owner files manually, then rerun.
Example fix
// before caveman mcp add & caveman mcp remove & # concurrent // after caveman mcp add && caveman mcp remove
Defensive patterns
Strategy: retry
Validate before calling
const owner = readOwner(lockPath);
if (owner && isLivePid(owner.pid)) console.error("wait for the running caveman mcp command to finish"); Type guard
const isLivePid = (pid: number) => { try { process.kill(pid, 0); return true; } catch (e) { return (e as NodeJS.ErrnoException).code !== "EPERM"; } }; Try / catch
try { mcpChange(); } catch (e) { if (String(e.message).includes("already running")) { await waitForProcessExit(ownerPid); retry(mcpChange); } } Prevention
- Run MCP config mutations sequentially per config path.
- Use a CI mutex when multiple jobs touch the same MCP config.
- Ensure lock/owner files are readable so staleness can be evaluated.
When it happens
Trigger: Starting an MCP config change while another live process holds the lock for the same canonical config path, or the lock state is ambiguous (owner file unreadable) and treated as live.
Common situations: Concurrent caveman MCP commands; a hung but running process holding the lock; permission errors reading the owner claim making staleness undecidable.
Related errors
- MCP config target changed while acquiring lock; refusing…
- MCP transaction state kept changing; refusing mutation
- changed while planning MCP update; refusing overwrite
- integration change already running for
- invalid MCP lock owner
AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20).
Data as JSON: /api/errors/50674cfd866a2ea4.
Report an issue: GitHub.
Appendix: source
Thrown at packages/cli/src/index.ts:7913
|| Object.keys(existing).sort().join("\0") !== keys.join("\0")
|| existing.schema_version !== 1
|| typeof existing.pid !== "number" || !Number.isInteger(existing.pid) || existing.pid <= 1
|| typeof existing.token !== "string"
|| !/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(existing.token)
|| existing.config_path !== canonicalPath
|| typeof existing.started_at !== "string"
|| new Date(existing.started_at).toISOString() !== existing.started_at
|| process.platform !== "win32" && ((lockStat.mode | ownerStat.mode) & 0o077) !== 0) {
throw new Error("invalid MCP lock owner");
}
try { process.kill(existing.pid, 0); }
catch (probeError) { stale = (probeError as NodeJS.ErrnoException).code === "ESRCH"; }
} catch {
// Populated claim is durable before publication, so malformed or
// ownerless lock can never be our crash residue. Never delete it.
stale = false;
}
if (!stale) throw new Error(`MCP config change already running for ${canonicalPath}`);
const quarantine = `${lock}.stale-${token}`;
try {
renameSync(lock, quarantine);
renameSync(claim, lock);
fsyncParentDirectory(lock);
process.stderr.write(`${mark("warn")} reclaimed stale MCP config lock for ${canonicalPath}\n`);
} catch {
throw new Error(`MCP config change already running for ${canonicalPath}`);
} finally {
try { rmSync(quarantine, { recursive: true, force: true }); } catch { /* isolated stale lock only */ }
}
}
return run();
} finally {
try { rmSync(claim, { recursive: true, force: true }); } catch { /* published or absent */ }
try {
const current = JSON.parse(readFileSync(join(lock, "owner.json"), "utf8")) as { token?: unknown };
if (current.token === token) {View on GitHub (pinned to 3ee70a1026)