affaan-m/ECC · error · Error

Refusing to read a file that changed during validation

Error message

Refusing to read a file that changed during validation: ${filePath}.

What it means

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.

Solutions

  1. Re-run the guided install once no other process is touching the files
  2. Kill concurrent install/setup processes and pause file-sync clients during install
  3. Restore the expected file from a backup or delete the changed state file and re-run the preview to regenerate it

Example fix

// before: concurrent process overwrites state during preview
runSetup(...)  // throws 'changed during validation'
// after
// stop other writers/sync, then:
runSetup(...)  // stable read succeeds
Defensive patterns

Strategy: retry

Validate before calling

// Quiesce writers first: check for locks/concurrent processes before reading
if (installLockExists()) throw new Error('Another install is running; wait and retry')

Try / catch

try {
  return snapshot(filePath)
} catch (err) {
  if (err.message.includes('changed during validation')) {
    await waitForQuiescence(filePath)
    return snapshot(filePath) // retry once after writers stop
  }
  throw err
}

Prevention

When it happens

Trigger: 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.

Common situations: Two guided installs running concurrently; Dropbox/OneDrive sync touching files mid-install; an editor or background formatter rewriting the file while the preview runs.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/7c17daec1728de76. Report an issue: GitHub.

Appendix: source

Thrown at scripts/lib/multi-harness-setup.js:99

    if (error && (error.code === 'ENOENT' || error.code === 'ENOTDIR')) return null;
    throw error;
  }

  try {
    const before = fs.fstatSync(descriptor);
    if (!before.isFile()) {
      throw new Error(`Refusing to read a non-file at ${filePath}.`);
    }
    const content = fs.readFileSync(descriptor);
    const after = fs.fstatSync(descriptor);
    const finalPathStat = fs.lstatSync(filePath);
    if (
      finalPathStat.isSymbolicLink()
      || !finalPathStat.isFile()
      || !sameFileIdentity(before, after)
      || !sameFileIdentity(after, finalPathStat)
    ) {
      throw new Error(`Refusing to read a file that changed during validation: ${filePath}.`);
    }
    return { content, stat: after };
  } finally {
    fs.closeSync(descriptor);
  }
}

function fingerprintFile(filePath) {
  const snapshot = readRegularFileSnapshot(filePath);
  if (!snapshot) return { exists: false, sha256: null };
  return {
    exists: true,
    sha256: crypto.createHash('sha256').update(snapshot.content).digest('hex'),
  };
}

function fingerprintInstallStateValue(state) {
  const content = Buffer.from(`${JSON.stringify(state, null, 2)}\n`);

View on GitHub (pinned to 8321021c54)