JuliusBrussee/caveman · error · Error
${path} changed during MCP update; refusing overwrite
Error message
${path} changed during MCP update; refusing overwrite What it means
durableReplaceFileIfUnchanged is a compare-and-swap style durable file replacement used during MCP updates. Before overwriting the target it re-reads the file and verifies its bytes still match the expected snapshot taken earlier. If another process (or user) modified the file between the snapshot and the write, the update aborts rather than clobbering concurrent changes.
Source
Thrown at packages/cli/src/index.ts:6819
function durableCreateFile(path: string, bytes: Buffer, mode = 0o600): void {
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
const temp = join(dirname(path), `.${basename(path)}.caveman-create-${process.pid}-${randomUUID()}.tmp`);
try {
durableAtomicWriteFile(temp, bytes, mode);
linkSync(temp, path);
fsyncParentDirectory(path);
} finally {
try { unlinkSync(temp); } catch { /* published or failed before temp creation */ }
}
}
function optionalBytesEqual(left: Buffer | null, right: Buffer | null): boolean {
return left === null ? right === null : right !== null && left.equals(right);
}
function durableReplaceFileIfUnchanged(path: string, expected: Buffer | null, next: Buffer | null, mode = 0o600): void {
const current = fileBytes(path);
if (!optionalBytesEqual(current, expected)) throw new Error(`${path} changed during MCP update; refusing overwrite`);
if (next === null) {
if (current !== null) {
const atDelete = fileBytes(path);
if (!optionalBytesEqual(atDelete, expected)) throw new Error(`${path} changed during MCP update; refusing removal`);
unlinkSync(path);
fsyncParentDirectory(path);
}
return;
}
// Prepare durable replacement first, then run final compare immediately
// before rename. Per-agent lock serializes Caveman writers; this CAS catches
// external edits observed before commit without erasing them.
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
const temp = join(dirname(path), `.${basename(path)}.caveman-cas-${process.pid}-${randomUUID()}.tmp`);
let fd: number | undefined;
try {
fd = openSync(temp, constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY, mode);View on GitHub (pinned to 5184b3d11a)
Solutions
- Re-run the MCP update so it takes a fresh snapshot of the current file and retries the replacement.
- Identify what else modified the file (another CLI run, sync service, editor) and serialize those operations.
- Acquire the MCP update lock (see lock owner validation) so only one update runs at a time.
- If the external change is unwanted, restore the expected content and retry; if wanted, merge it manually then re-run.
Example fix
// before: concurrent writes clobber each other writeFileSync(mcpConfigPath, next); // after: compare-and-swap via durableReplaceFileIfUnchanged with a fresh expected snapshot const expected = fileBytes(mcpConfigPath); durableReplaceFileIfUnchanged(mcpConfigPath, expected, next);
Defensive patterns
Strategy: retry
Validate before calling
const expected = fileBytes(path);
if (!optionalBytesEqual(fileBytes(path), expected)) {
console.error('file changed externally; re-read and retry the update');
} Type guard
function isUnchanged(path: string, expected: Buffer | null): boolean {
return optionalBytesEqual(fileBytes(path), expected);
} Try / catch
try {
durableReplaceFileIfUnchanged(path, expected, next);
} catch (err) {
if (String(err.message).includes('changed during MCP update')) {
// re-read fresh snapshot and retry once, or surface conflict to the user
} else throw err;
} Prevention
- Run MCP updates under the update lock so only one writer exists.
- Exclude config directories from file-sync tools (Dropbox/iCloud/OneDrive).
- Close editors with open buffers on the config file before updating.
- Retry updates automatically on this error with a fresh snapshot.
When it happens
Trigger: Running an MCP update that calls durableReplaceFileIfUnchanged while the target file at path was modified after the expected snapshot was captured — e.g. a concurrent CLI process, an editor auto-save, or another tool rewrote the config between read and replace.
Common situations: Two CLI instances updating MCP config simultaneously; a sync tool (Dropbox/iCloud) touching the config file mid-update; an editor with unsaved-buffer auto-write racing the update.
Understand the failure class
Background: Checksum mismatch errors: "checksum verification failed", "digest mismatch", "expected vs actual checksum" — what they mean and how to fix them — this error's family across 41 libraries.
Related errors
- ${path} changed during MCP update; refusing removal
- invalid MCP lock owner
- cave_subagent_concurrency_limit_invalid
- cave_subagent_concurrency_limit
- filter monitor.id is required
AI-assisted analysis of JuliusBrussee/caveman@5184b3d11a (2026-09-06).
Data as JSON: /api/errors/ca5abca2c0996dc1.
Report an issue: GitHub.