{"record":{"id":"f5ab2d70c636ec62","repo":"affaan-m/ECC","slug":"refusing-to-action-managed-destination-changed-during-the","errorCode":null,"errorMessage":"Refusing to ${action}: managed destination changed during the write.","messagePattern":"Refusing to (.+?): managed destination changed during the write\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/lib/install-lifecycle.js","lineNumber":454,"sourceCode":"    { allowFinalSymlink }\n  );\n  return finalDestination.exists ? finalDestination.managedPath : null;\n}\n\nfunction hasSameFileIdentity(leftStat, rightStat) {\n  return leftStat.dev === rightStat.dev && leftStat.ino === rightStat.ino;\n}\n\nfunction createChangedDestinationError(action) {\n  return new Error(\n    `Refusing to ${action}: managed destination changed during the write.`\n  );\n}\n\nfunction getStableParentStat(filePath, action) {\n  const parentStat = fs.lstatSync(path.dirname(filePath));\n  if (!parentStat.isDirectory() || parentStat.isSymbolicLink()) {\n    throw createChangedDestinationError(action);\n  }\n  return parentStat;\n}\n\nfunction assertPinnedWriteDestination(\n  filePath,\n  fileDescriptor,\n  expectedParentStat,\n  trustedRoot,\n  action\n) {\n  const liveDestination = getManagedDestination(filePath, trustedRoot, action);\n  if (path.resolve(liveDestination.managedPath) !== path.resolve(filePath)) {\n    throw createChangedDestinationError(action);\n  }\n\n  const liveParentStat = getStableParentStat(filePath, action);\n  if (!hasSameFileIdentity(expectedParentStat, liveParentStat)) {","sourceCodeStart":436,"sourceCodeEnd":472,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/install-lifecycle.js#L436-L472","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Re-run the install/repair command; the race is usually transient and a fresh run will re-snapshot a stable state.","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).","Ensure no other installer, package manager, or file-sync process is mutating the target tree concurrently; serialize installs.","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."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"const parent = path.dirname(destination);\nlet st;\ntry { st = fs.lstatSync(parent); } catch { throw new Error(`Parent missing before write: ${parent}`); }\nif (!st.isDirectory() || st.isSymbolicLink()) {\n  throw new Error(`Refusing to write: ${parent} is not a real directory (file or symlink present)`);\n}","typeGuard":null,"tryCatchPattern":"let attempts = 0;\nwhile (attempts < 3) {\n  try { performManagedWrite(filePath); break; }\n  catch (err) {\n    if (!err.message.includes('managed destination changed')) throw err;\n    if (++attempts === 3) throw new Error(`Destination ${filePath} keeps changing; stop concurrent writers and retry.`);\n    await new Promise(r => setTimeout(r, 250 * attempts));\n  }\n}","preventionTips":["Run only one installer instance at a time; use a lock file for CI or scheduled runs.","Exclude managed install directories from file-sync tools (Dropbox, OneDrive, iCloud) and realtime backup agents.","Avoid symlink-farm layouts (stow/dotfiles) for paths the installer manages, or configure its trusted root accordingly.","Re-run the install after any manual restructuring of the target tree."],"tags":["filesystem","race-condition","symlink"],"backgroundTag":"invalid-state-transition","analyzedSha":"8321021c54d670126ce3b2969d5deb880b4b0c2a","analyzedAt":"2026-09-16T10:08:13.343Z","contentChangedAt":"2026-09-16T10:08:13.343Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}