affaan-m/ECC · error · Error

Refusing to : managed destination changed during the write.

Error message

Refusing to ${action}: managed destination changed during the write.

What it means

Before writing a managed file, install-lifecycle.js pins the identity of the destination's parent directory so it can detect concurrent modification ('TOCTOU' protection). getStableParentStat lstats the parent directory and throws this error if the parent is not a directory or is itself a symlink — meaning the path layout changed between planning and writing, or was replaced by a symlink pointing elsewhere. The library refuses the write rather than following a possibly attacker-controlled symlink.

Solutions

  1. Re-run the install/repair command; the race is usually transient and a fresh run will re-snapshot a stable state.
  2. Inspect path.dirname(filePath): remove or replace whatever now occupies it if it is a file or an unintended symlink (e.g. ls -la to spot symlinks).
  3. Ensure no other installer, package manager, or file-sync process is mutating the target tree concurrently; serialize installs.
  4. If the destination is symlink-managed (dotfiles/stow), point the installer at the real directory or adjust its trusted-root configuration instead of letting it write through the symlink.
Defensive patterns

Strategy: retry

Validate before calling

const parent = path.dirname(destination);
let st;
try { st = fs.lstatSync(parent); } catch { throw new Error(`Parent missing before write: ${parent}`); }
if (!st.isDirectory() || st.isSymbolicLink()) {
  throw new Error(`Refusing to write: ${parent} is not a real directory (file or symlink present)`);
}

Try / catch

let attempts = 0;
while (attempts < 3) {
  try { performManagedWrite(filePath); break; }
  catch (err) {
    if (!err.message.includes('managed destination changed')) throw err;
    if (++attempts === 3) throw new Error(`Destination ${filePath} keeps changing; stop concurrent writers and retry.`);
    await new Promise(r => setTimeout(r, 250 * attempts));
  }
}

Prevention

When it happens

Trigger: Calling the managed write path (via assertPinnedWriteDestination → getStableParentStat, reached from writeFileNoFollow) when path.dirname(filePath) is: (a) a regular file or other non-directory (e.g. someone created a file where a directory was expected), (b) a symlink to a directory. Requires that an earlier expectedParentStat snapshot existed and the live stat now differs or is invalid.

Common situations: Another process (editor, sync tool like Dropbox/OneDrive, a parallel install) replaced or removed directories under the install target while the installer ran; a symlink farm or relocated install target (e.g. ~/.claude symlinked to a dotfiles-managed directory); running the installer twice concurrently; antivirus or backup software touching paths mid-write.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/f5ab2d70c636ec62. Report an issue: GitHub.

Appendix: source

Thrown at scripts/lib/install-lifecycle.js:454

    { allowFinalSymlink }
  );
  return finalDestination.exists ? finalDestination.managedPath : null;
}

function hasSameFileIdentity(leftStat, rightStat) {
  return leftStat.dev === rightStat.dev && leftStat.ino === rightStat.ino;
}

function createChangedDestinationError(action) {
  return new Error(
    `Refusing to ${action}: managed destination changed during the write.`
  );
}

function getStableParentStat(filePath, action) {
  const parentStat = fs.lstatSync(path.dirname(filePath));
  if (!parentStat.isDirectory() || parentStat.isSymbolicLink()) {
    throw createChangedDestinationError(action);
  }
  return parentStat;
}

function assertPinnedWriteDestination(
  filePath,
  fileDescriptor,
  expectedParentStat,
  trustedRoot,
  action
) {
  const liveDestination = getManagedDestination(filePath, trustedRoot, action);
  if (path.resolve(liveDestination.managedPath) !== path.resolve(filePath)) {
    throw createChangedDestinationError(action);
  }

  const liveParentStat = getStableParentStat(filePath, action);
  if (!hasSameFileIdentity(expectedParentStat, liveParentStat)) {

View on GitHub (pinned to 8321021c54)