abhigyanpatwari/GitNexus · error

${filename} moved or was replaced while being opened

Error message

${filename} moved or was replaced while being opened

What it means

readRepoControlFile compares the identity (dev/ino) of the opened fd with a fresh lstat of the path; if they differ, or the entry is no longer a single-link regular file, the file changed identity while being opened and this error is thrown. It guarantees the content streamed comes from exactly the file that was validated.

Source

Thrown at gitnexus/src/config/repo-control-file.ts:68

      stream.pause();
      stream.once('open', (fd) => {
        try {
          const opened = fs.fstatSync(fd);
          if (!opened.isFile()) throw new Error(`${filename} must be a regular file`);
          if (opened.nlink !== 1) throw new Error(`${filename} must not be a hard link`);
          if (opened.size > MAX_REPO_CONTROL_FILE_BYTES) {
            throw new Error(`${filename} exceeds ${MAX_REPO_CONTROL_FILE_BYTES} bytes`);
          }

          const entry = fs.lstatSync(requested);
          if (entry.isSymbolicLink()) throw new Error(`${filename} must not be a symbolic link`);
          if (
            !entry.isFile() ||
            entry.nlink !== 1 ||
            entry.dev !== opened.dev ||
            entry.ino !== opened.ino
          ) {
            throw new Error(`${filename} moved or was replaced while being opened`);
          }
          const canonicalFile = fs.realpathSync(requested);
          const canonicalRelative = path.relative(canonicalRoot, canonicalFile);
          if (canonicalRelative.startsWith('..') || path.isAbsolute(canonicalRelative)) {
            throw new Error(`${filename} resolves outside the repository root`);
          }
          const canonical = fs.statSync(canonicalFile);
          if (
            canonical.nlink !== 1 ||
            canonical.dev !== opened.dev ||
            canonical.ino !== opened.ino
          ) {
            throw new Error(`${filename} moved or was replaced while being opened`);
          }

          validated = true;
          stream.resume();
        } catch (error) {

View on GitHub (pinned to 52924ef12c)

Solutions

  1. Serialize access: ensure no editor, git operation, or generator rewrites the control file during analysis.
  2. Re-run the command after concurrent writes settle — the race is transient.
  3. Use atomic-in-place writes (write to temp, then rename only when idle) or stop rewriting files the reader watches.
  4. Investigate unexpected writers (`lsof <file>`, audit logs) if the race recurs without obvious cause.

Example fix

// before: editor races the reader via rename
mv .gitnexusrc.tmp .gitnexusrc  # during read
// after: finalize writes before starting analysis
# wait for editor/git to finish, then run the command
Defensive patterns

Strategy: retry

Validate before calling

// No pure pre-check can prevent a mid-open swap; minimize the window by
// ensuring no concurrent writers, then read:
import fs from 'node:fs';
const a = fs.lstatSync(controlFilePath);
// ...proceed only when no git/editor/generator process is active on the repo

Try / catch

async function readStable(root: string, filename: string, tries = 3): Promise<string> {
  for (let i = 0; ; i++) {
    try { return await readRepoControlFile(root, filename); }
    catch (err) {
      if (i < tries && (err as Error).message.includes('moved or was replaced')) {
        await new Promise(r => setTimeout(r, 200 * (i + 1)));
        continue;
      }
      throw err;
    }
  }
}

Prevention

When it happens

Trigger: Between createReadStream opening the fd and the follow-up lstatSync(requested), the path is renamed, deleted-and-recreated, or swapped, so entry.dev/entry.ino no longer match opened.dev/opened.ino (or entry fails isFile()/nlink===1).

Common situations: Editor save-via-rename (write temp + rename) racing the read; `git checkout`/branch switch replacing the file mid-run; a build script regenerating the config concurrently; malicious file-swap during CI.

Related errors


AI-assisted analysis of abhigyanpatwari/GitNexus@52924ef12c (2026-09-01). Data as JSON: /api/errors/24ad9eb909c6e8da. Report an issue: GitHub.