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

  1. Re-run the MCP update so a fresh snapshot is taken and the removal decision is re-evaluated against current content.
  2. Check whether another process or sync service modified the file and coordinate/serialize updates.
  3. If the file legitimately changed and should be kept, skip the removal manually or adjust update inputs.
  4. 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

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


AI-assisted analysis of JuliusBrussee/caveman@5184b3d11a (2026-09-06). Data as JSON: /api/errors/fd1f5f12140ed824. Report an issue: GitHub.