{"record":{"id":"3a0ddeaefb0587f7","repo":"affaan-m/ECC","slug":"refusing-to-hash-changed-install-destination-op","errorCode":null,"errorMessage":"Refusing to hash changed install destination: ${operation.destinationPath}","messagePattern":"Refusing to hash changed install destination: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"scripts/lib/install/apply.js","lineNumber":124,"sourceCode":"  try {\n    descriptor = fs.openSync(operation.destinationPath, flags);\n  } catch (error) {\n    if (error.code === 'ENOENT') {\n      return null;\n    }\n    throw error;\n  }\n\n  try {\n    const openedStat = fs.fstatSync(descriptor, { bigint: true });\n    const finalPathStat = fs.lstatSync(operation.destinationPath, { bigint: true });\n    if (finalPathStat.isSymbolicLink() || !finalPathStat.isFile()) {\n      return null;\n    }\n    const identityMatches = openedStat.ino === finalPathStat.ino\n      && (!openedStat.dev || !finalPathStat.dev || openedStat.dev === finalPathStat.dev);\n    if (!openedStat.isFile() || !identityMatches) {\n      throw new Error(\n        `Refusing to hash changed install destination: ${operation.destinationPath}`\n      );\n    }\n    // Revalidate the full path after opening. The descriptor pins the file so\n    // the digest and metadata refer to the same object.\n    assertSafeInstallOperation(plan, operation);\n    assertSafeClaudeSkillOperation(plan, operation);\n    return fs.readFileSync(descriptor);\n  } finally {\n    fs.closeSync(descriptor);\n  }\n}\n\nfunction stateWithContentDigests(state, plan) {\n  const currentDestinations = new Set((plan.operations || [])\n    .filter(operation => operation.destinationPath)\n    .map(operation => {\n      const resolved = path.resolve(operation.destinationPath);","sourceCodeStart":106,"sourceCodeEnd":142,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/install/apply.js#L106-L142","documentation":"readInstalledFileNoFollow opens the install destination without following symlinks and then re-stats the final path to verify the opened file is still the same regular file (same inode/dev). If the opened descriptor and the path-resolved stat disagree — the destination was swapped, replaced, or is no longer a regular file between open and stat — hashing would attribute a digest to the wrong bytes, so it refuses. This is a TOCTOU (time-of-check-time-of-use) integrity guard for install destinations.","triggerScenarios":"Another process (editor, sync tool, concurrent installer run) replaces or renames the destination file after openFileSync but before fstat/lstat verification; destination swapped with a symlink or non-file; race between two concurrent applyInstallPlan runs on the same target.","commonSituations":"Running the installer while a cloud-sync client (Dropbox/OneDrive) rewrites the target directory; two installer processes running in parallel; antivirus or a build watcher touching the destination mid-install.","solutions":["Re-run the install — transient races usually pass on retry once nothing else touches the directory.","Ensure no other process modifies the install target while the installer runs (close sync clients/watchers).","Run only one installer instance at a time against the same targetRoot.","If it persists, delete the destination and re-apply the plan so a fresh file is installed."],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  applyInstallPlan(plan);\n} catch (e) {\n  if (e.message.startsWith('Refusing to hash changed install destination')) {\n    // transient race: pause and retry once with no concurrent writers\n    await new Promise(r => setTimeout(r, 250));\n    return applyInstallPlan(plan);\n  }\n  throw e;\n}","preventionTips":["Run only one installer process per target root at a time.","Pause file-sync clients, watchers, and antivirus scans on the install directory during installs.","Retry the install after a transient failure; if it repeats, investigate what is mutating the destination."],"tags":["filesystem","race-condition","integrity"],"backgroundTag":"internal-invariant-violation","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"}