JuliusBrussee/caveman · error · Error
${path} changed during MCP update; refusing removal
Error message
${path} changed during MCP update; refusing removal What it means
When an MCP update determines a file should be removed (next === null), it double-checks the file's bytes immediately before unlinking. If the bytes no longer match the expected snapshot, the file was modified concurrently and the removal is refused to avoid deleting someone else's new content.
Source
Thrown at packages/cli/src/index.ts:6823
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);
writeFileSync(fd, next);
fchmodSync(fd, mode);
fsyncSync(fd);
closeSync(fd);View on GitHub (pinned to 5184b3d11a)
Solutions
- Re-run the MCP update so a fresh snapshot is taken and the removal decision is re-evaluated against current content.
- Check whether another process or sync service modified the file and coordinate/serialize updates.
- If the file legitimately changed and should be kept, skip the removal manually or adjust update inputs.
- Run the update under the MCP lock to exclude concurrent writers.
Example fix
// before: blind delete can drop concurrent edits unlinkSync(configPath); // after: verify before delete const current = fileBytes(configPath); if (optionalBytesEqual(current, expected)) unlinkSync(configPath);
Defensive patterns
Strategy: retry
Validate before calling
if (fileBytes(path) !== null && !optionalBytesEqual(fileBytes(path), expected)) {
console.error('target modified externally; skip removal and re-evaluate');
} Type guard
function isSafeToRemove(path: string, expected: Buffer): boolean {
return optionalBytesEqual(fileBytes(path), expected);
} Try / catch
try {
durableReplaceFileIfUnchanged(path, expected, null);
} catch (err) {
if (String(err.message).includes('refusing removal')) {
// re-run update so removal is re-decided against current content
} else throw err;
} Prevention
- Serialize removals and updates through the MCP lock.
- Do not hand-edit files the updater manages while an update runs.
- Disable sync tools for the config directory during maintenance.
- Re-run the update after any concurrent-modification error.
When it happens
Trigger: durableReplaceFileIfUnchanged called with next=null (removal path) while the file at path was written between the initial fileBytes read and the pre-delete re-read — concurrent update, editor save, or sync-tool write.
Common situations: Removing an obsolete MCP entry while another process has just recreated/edited that same config file; file-sync software resurrecting or modifying the file during cleanup.
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 overwrite
- 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/fd1f5f12140ed824.
Report an issue: GitHub.