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
- Serialize access: ensure no editor, git operation, or generator rewrites the control file during analysis.
- Re-run the command after concurrent writes settle — the race is transient.
- Use atomic-in-place writes (write to temp, then rename only when idle) or stop rewriting files the reader watches.
- 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
- Serialize writes: no git operations or editors touching the file during analysis.
- Use atomic single-rename writes and avoid rewriting files that are read frequently.
- Back off and retry — the race window is tiny, so a short delay usually succeeds.
- Investigate persistent offenders with lsof/audit tooling.
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
- Path traversal denied
- Analyzer identity directory changed while it was read: ${can
- Analyzer identity input changed while it was being read: ${c
- Analyzer identity input changed while it was being hashed: $
- Analyzer runtime payload directory is unavailable: ${absolut
AI-assisted analysis of abhigyanpatwari/GitNexus@52924ef12c (2026-09-01).
Data as JSON: /api/errors/24ad9eb909c6e8da.
Report an issue: GitHub.