abhigyanpatwari/GitNexus · error · Error

Analyzer identity input changed while it was being read: ${c

Error message

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

What it means

resolveAnalyzerRunnerIdentity() double-reads every identity input (manifests, lockfile) and compares full stat snapshots (dev/ino/size/mtimeNs/ctimeNs) taken before and after the read; if they differ on both of two attempts, the file is considered concurrently mutated and the resolve aborts. This is a deliberate TOCTOU guard: stamping an identity from a file that changed mid-read would make the receipt meaningless. The error names the exact file that refused to stabilize.

Source

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

  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);
  const bytes = stateSize(before.target, candidate);
  if (budget.bytes + bytes > maxBytes) {
    throw new Error(`Analyzer runtime scan exceeded ${maxBytes} bytes: ${candidate}`);
  }
  const stable = readStableFile(candidate);
  budget.bytes += stable.bytes.length;
  return stable;
}

/** Hash a stable file through a fixed-size buffer instead of materializing it. */

View on GitHub (pinned to aac7515d2a)

Solutions

  1. Re-run the analyze/status command once the concurrent package-manager or watcher process has finished — the error is transient by design.
  2. Serialize the steps: complete 'npm ci'/'npm install' (and any build that rewrites dist/) before invoking gitnexus.
  3. Stop or pause file watchers (tsx watch, nodemon, dev servers) and antivirus/indexing of the gitnexus install directory during the run.
  4. If it persists on an idle filesystem, verify the disk/fs is not corrupt (stat metadata flapping) with 'stat <file>' twice and comparing.
  5. Programmatic callers can wrap resolveAnalyzerRunnerIdentity in a bounded retry (see exampleFix).

Example fix

// before
const identity = resolveAnalyzerRunnerIdentity(import.meta.url);

// after (bounded retry for the transient TOCTOU window)
function resolveStable(url: string, attempts = 3): ReturnType<typeof resolveAnalyzerRunnerIdentity> {
  for (let i = 0; ; i += 1) {
    try {
      return resolveAnalyzerRunnerIdentity(url);
    } catch (error) {
      if (i === attempts - 1 || !/^Analyzer identity (directory|input) changed while/.test(String((error as Error).message))) {
        throw error;
      }
    }
  }
}
Defensive patterns

Strategy: retry

Type guard

function isIdentityInputChangedError(error: unknown): boolean {
  return error instanceof Error && /^Analyzer identity input changed while it was being read:/.test(error.message);
}

Try / catch

try {
  identity = resolveAnalyzerRunnerIdentity(import.meta.url);
} catch (error) {
  if (!isIdentityInputChangedError(error)) throw error;
  await sleep(250);
  identity = resolveAnalyzerRunnerIdentity(import.meta.url); // one bounded retry
}

Prevention

When it happens

Trigger: readStableFile() is reached via resolveAnalyzerRunnerIdentity -> collectDependencyInputs -> readManifest/findNearestPackageLock while hashing a package.json/package-lock.json whose stat state changes between snapshotReadableFile() calls twice in a row. Typical producers: npm/pnpm/yarn install or uninstall running concurrently with 'gitnexus analyze' or 'gitnexus status', a file watcher (tsx watch, dev server, antivirus) rewriting the file, or a package manager hard-linking/retargeting files during the scan.

Common situations: CI pipelines that run 'npm install' and 'gitnexus analyze' in parallel or back-to-back without waiting; Docker builds where layer materialization touches mtimes during the run; Windows Defender or indexer touching ctime/mtime; a second terminal running an install while a server analyze-worker resolves identity.

Related errors


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