{"record":{"id":"7c17daec1728de76","repo":"affaan-m/ECC","slug":"refusing-to-read-a-file-that-changed-during-validation-multi","errorCode":null,"errorMessage":"Refusing to read a file that changed during validation: ${filePath}.","messagePattern":"Refusing to read a file that changed during validation: (.+?)\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/lib/multi-harness-setup.js","lineNumber":99,"sourceCode":"    if (error && (error.code === 'ENOENT' || error.code === 'ENOTDIR')) return null;\n    throw error;\n  }\n\n  try {\n    const before = fs.fstatSync(descriptor);\n    if (!before.isFile()) {\n      throw new Error(`Refusing to read a non-file at ${filePath}.`);\n    }\n    const content = fs.readFileSync(descriptor);\n    const after = fs.fstatSync(descriptor);\n    const finalPathStat = fs.lstatSync(filePath);\n    if (\n      finalPathStat.isSymbolicLink()\n      || !finalPathStat.isFile()\n      || !sameFileIdentity(before, after)\n      || !sameFileIdentity(after, finalPathStat)\n    ) {\n      throw new Error(`Refusing to read a file that changed during validation: ${filePath}.`);\n    }\n    return { content, stat: after };\n  } finally {\n    fs.closeSync(descriptor);\n  }\n}\n\nfunction fingerprintFile(filePath) {\n  const snapshot = readRegularFileSnapshot(filePath);\n  if (!snapshot) return { exists: false, sha256: null };\n  return {\n    exists: true,\n    sha256: crypto.createHash('sha256').update(snapshot.content).digest('hex'),\n  };\n}\n\nfunction fingerprintInstallStateValue(state) {\n  const content = Buffer.from(`${JSON.stringify(state, null, 2)}\\n`);","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/affaan-m/ECC/blob/8321021c54d670126ce3b2969d5deb880b4b0c2a/scripts/lib/multi-harness-setup.js#L81-L117","documentation":"readRegularFileSnapshot re-stats the file after reading and compares inode identity (before, after, and the on-disk path stat) to detect concurrent modification or a path/symlink swap during the read. Any mismatch throws this error so the installer never operates on a file whose contents or identity changed while being validated. It is a TOCTOU protection for install-state and destination files.","triggerScenarios":"Another process (editor, sync client, concurrent install run) modifies the target file or swaps it for a symlink/different inode between the initial fstat, the read, and the final lstat of the path.","commonSituations":"Two guided installs running concurrently; Dropbox/OneDrive sync touching files mid-install; an editor or background formatter rewriting the file while the preview runs.","solutions":["Re-run the guided install once no other process is touching the files","Kill concurrent install/setup processes and pause file-sync clients during install","Restore the expected file from a backup or delete the changed state file and re-run the preview to regenerate it"],"exampleFix":"// before: concurrent process overwrites state during preview\nrunSetup(...)  // throws 'changed during validation'\n// after\n// stop other writers/sync, then:\nrunSetup(...)  // stable read succeeds","handlingStrategy":"retry","validationCode":"// Quiesce writers first: check for locks/concurrent processes before reading\nif (installLockExists()) throw new Error('Another install is running; wait and retry')","typeGuard":null,"tryCatchPattern":"try {\n  return snapshot(filePath)\n} catch (err) {\n  if (err.message.includes('changed during validation')) {\n    await waitForQuiescence(filePath)\n    return snapshot(filePath) // retry once after writers stop\n  }\n  throw err\n}","preventionTips":["Use an install lock to serialize guided install runs","Pause file-sync clients (Dropbox/OneDrive) during install","Run installs outside of periods when editors/formatters touch the target directory"],"tags":["filesystem","race-condition","safety"],"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"}