{"record":{"id":"3d9f764c08811f38","repo":"affaan-m/ECC","slug":"refusing-to-action-managed-destination-changed","errorCode":null,"errorMessage":"Refusing to ${action}: managed destination changed before removal; replacement preserved at ${quarantinePath}.","messagePattern":"Refusing to (.+?): managed destination changed before removal; replacement preserved at (.+?)\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/lib/install-lifecycle.js","lineNumber":631,"sourceCode":"    path.dirname(managedDestination.canonicalRoot),\n    '.ecc-remove-'\n  ));\n  const quarantinePath = path.join(quarantineDir, path.basename(finalDestination));\n\n  try {\n    fs.renameSync(finalDestination, quarantinePath);\n  } catch (error) {\n    fs.rmdirSync(quarantineDir);\n    throw error;\n  }\n\n  const quarantinedStat = fs.lstatSync(quarantinePath, { bigint: true });\n  if (!hasSameFileIdentity(expectedStat, quarantinedStat)) {\n    try {\n      fs.renameSync(quarantinePath, finalDestination);\n      fs.rmdirSync(quarantineDir);\n    } catch (_restoreError) {\n      throw new Error(\n        `Refusing to ${action}: managed destination changed before removal; replacement preserved at ${quarantinePath}.`\n      );\n    }\n    throw createChangedDestinationError(action);\n  }\n\n  if (quarantinedStat.isDirectory() && !options.recursive) {\n    fs.rmdirSync(quarantinePath);\n  } else {\n    fs.rmSync(quarantinePath, options);\n  }\n  fs.rmdirSync(quarantineDir);\n  return finalDestination;\n}\n\nfunction deepMergeJson(baseValue, patchValue) {\n  if (!isPlainObject(baseValue) || !isPlainObject(patchValue)) {\n    return cloneJsonValue(patchValue);","sourceCodeStart":613,"sourceCodeEnd":649,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/install-lifecycle.js#L613-L649","documentation":"During removal, the file is first renamed to a quarantine path; its identity (per lstat with bigint) is compared against the expected stat captured earlier. If the quarantined file no longer matches the expected identity — meaning the managed destination changed underneath the install lifecycle — the library tries to restore it. If the restore (rename back / rmdir) also fails, it throws this error naming the quarantine path where the replacement file was preserved, refusing to delete data it cannot safely account for.","triggerScenarios":"Another process (editor save, sync tool, concurrent install/uninstall) modifies or replaces the managed file between the identity snapshot and the removal, AND the rename-back to finalDestination or rmdir of the quarantine dir fails (e.g. destination recreated by another process, permissions issue, AV lock on Windows).","commonSituations":"A file watcher/editor auto-reformats settings.json during uninstall; Dropbox/OneDrive sync locking or rewriting the file; two installs or an install plus uninstall running concurrently on the same machine; antivirus briefly holding the file open.","solutions":["Inspect the file at the quarantine path printed in the message, merge/preserve any changes you need, then delete it manually.","Close editors, sync clients, and other processes touching the file, then re-run the uninstall/removal operation.","Re-run the lifecycle operation once nothing holds locks on the destination; on Windows check antivirus exclusion for the directory.","If changes were intentional, don't remove the file — update install-state (or re-run install) so the recorded state matches the on-disk file."],"exampleFix":"// after error: replacement preserved at /tmp/ecc-quarantine-xyz/settings.json\ncp /tmp/ecc-quarantine-xyz/settings.json ~/.claude/settings.json.manual-backup\nrm -rf /tmp/ecc-quarantine-xyz\n# close editors/sync, then retry the operation","handlingStrategy":"try-catch","validationCode":"const fs = require('fs');\nfunction assertQuiescentFile(p) {\n  const s1 = fs.statSync(p, { bigint: true });\n  return () => {\n    const s2 = fs.statSync(p, { bigint: true });\n    if (s1.ino !== s2.ino || s1.size !== s2.size || s1.mtimeNs !== s2.mtimeNs) {\n      throw new Error('File changed during operation; close other processes and retry');\n    }\n  };\n}","typeGuard":null,"tryCatchPattern":"try {\n  await uninstallManagedHooks(op);\n} catch (err) {\n  if (/changed before removal; replacement preserved/.test(err.message)) {\n    const q = /preserved at (\\S+)\\.$/.exec(err.message)[1];\n    console.error(`Merge or delete the preserved file at ${q}, then re-run.`);\n  } else throw err;\n}","preventionTips":["Close editors, file watchers, and sync clients that touch managed files before running uninstall/repair.","Do not run install and uninstall concurrently on the same machine.","After hitting this error, always inspect the preserved quarantine file before deleting anything."],"tags":["concurrency","file-system","state"],"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"}