{"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":"Error","httpStatus":null,"severity":"error","filePath":"scripts/lib/install/apply.js","lineNumber":107,"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  return {\n    ...state,\n    operations: (state.operations || []).map(operation => {\n      if (!operation.destinationPath) {","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/affaan-m/ECC/blob/06c5e118c4d3e6c3b7f9445f973a2194c82de193/scripts/lib/install/apply.js#L89-L125","documentation":"TOCTOU guard in readInstalledFileNoFollow: fstat/lstat identity comparison detects the destination changed, is a symlink, or is no longer a regular file between open and stat, so hashing (and trusting) its content is refused.","triggerScenarios":"Thrown at scripts/lib/install/apply.js:107 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["The destination file changed after hashing; re-run the install so state is recomputed.","If you edited the managed file manually, restore it or run a repair so ECC can manage it safely."],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"06c5e118c4d3e6c3b7f9445f973a2194c82de193","analyzedAt":"2026-08-18T11:27:13.915Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}