abhigyanpatwari/GitNexus · error · Error

Analyzer identity directory changed while it was read: ${can

Error message

Analyzer identity directory changed while it was read: ${candidate}

What it means

Analyzer identity fingerprinting double-snapshots each directory (stat state before and after listing entries) and retries once internally; if both attempts observe the directory changing mid-read, it gives up and throws. It is a TOCTOU guard: the identity digest must be computed from a stable view of the build/dependency directories, so concurrent mutation makes the result untrustworthy.

Source

Thrown at gitnexus/src/core/analyzer-identity.ts:517

  for (const entry of entries) updateCanonicalFrame(hash, [entry.name, entry.kind]);
  return `sha256:${hash.digest('hex')}`;
}

function directoryEntriesDigest(candidate: string): string {
  return directoryEntriesDigestFrom(readdirSync(candidate, { withFileTypes: true }));
}

function snapshotDirectoryInventory(candidate: string): {
  state: StatState;
  entriesDigest: string;
} {
  for (let attempt = 0; attempt < 2; attempt += 1) {
    const before = snapshotDirectory(candidate);
    const entriesDigest = directoryEntriesDigest(candidate);
    const after = snapshotDirectory(candidate);
    if (isDeepStrictEqual(before, after)) return { state: after, entriesDigest };
  }
  throw new Error(`Analyzer identity directory changed while it was read: ${candidate}`);
}

function readStableFile(candidate: string): { bytes: Buffer; state: ReadableFileState } {
  for (let attempt = 0; attempt < 2; attempt += 1) {
    const before = snapshotReadableFile(candidate);
    const bytes = readFileSync(candidate);
    const after = snapshotReadableFile(candidate);
    if (isDeepStrictEqual(before, after)) return { bytes, state: after };
  }
  throw new Error(`Analyzer identity input changed while it was being read: ${candidate}`);
}

function readStableFileWithinBudget(
  candidate: string,
  budget: RuntimeArtifactScanBudget,
  maxBytes: number,
): { bytes: Buffer; state: ReadableFileState } {
  const before = snapshotReadableFile(candidate);

View on GitHub (pinned to aac7515d2a)

Solutions

  1. Simply re-run analyze — the error is transient and single-shot interference almost never repeats
  2. Serialize the work: finish installs/builds (or cancel watchers) before invoking analyze; don't run two analyze processes on the same tree
  3. For Docker bind mounts, prefer named volumes or run analyze before mounting, to avoid mtime churn from host sync
  4. If it recurs, identify the writer: lsof +D <path> or check what regenerates the directory during the run
Defensive patterns

Strategy: retry

Try / catch

for (let attempt = 1; attempt <= 3; attempt += 1) {
  try {
    return await computeAnalyzerIdentity(request);
  } catch (err) {
    const msg = (err as Error).message ?? '';
    const transient = msg.includes('changed while it was read');
    if (!transient || attempt === 3) throw err;
    await delay(250 * attempt); // let the concurrent writer finish
  }
}

Prevention

When it happens

Trigger: Running analyze while a build, npm/yarn/pnpm install, code generator, formatter-on-save, or another gitnexus process is writing into the directories being fingerprinted (the analyzer build root and dependency directories); hot rebuild loops with tight write cadence can defeat both attempts.

Common situations: CI running analyze in parallel with an install step or a bundler watch; local dev with a file watcher regenerating outputs continuously; two analyze runs racing on the same checkout; Docker bind mounts with slow metadata sync flipping mtimes during the snapshot.

Related errors


AI-assisted analysis of abhigyanpatwari/GitNexus@aac7515d2a (2026-08-20). Data as JSON: /api/errors/f5ea3c49d16f1545. Report an issue: GitHub.