{"record":{"id":"81ca825e683f5134","repo":"abhigyanpatwari/GitNexus","slug":"analyzer-identity-input-changed-while-it-was-being","errorCode":null,"errorMessage":"Analyzer identity input changed while it was being read: ${candidate}","messagePattern":"Analyzer identity input changed while it was being read: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"gitnexus/src/core/analyzer-identity.ts","lineNumber":527,"sourceCode":"  entriesDigest: string;\n} {\n  for (let attempt = 0; attempt < 2; attempt += 1) {\n    const before = snapshotDirectory(candidate);\n    const entriesDigest = directoryEntriesDigest(candidate);\n    const after = snapshotDirectory(candidate);\n    if (isDeepStrictEqual(before, after)) return { state: after, entriesDigest };\n  }\n  throw new Error(`Analyzer identity directory changed while it was read: ${candidate}`);\n}\n\nfunction readStableFile(candidate: string): { bytes: Buffer; state: ReadableFileState } {\n  for (let attempt = 0; attempt < 2; attempt += 1) {\n    const before = snapshotReadableFile(candidate);\n    const bytes = readFileSync(candidate);\n    const after = snapshotReadableFile(candidate);\n    if (isDeepStrictEqual(before, after)) return { bytes, state: after };\n  }\n  throw new Error(`Analyzer identity input changed while it was being read: ${candidate}`);\n}\n\nfunction readStableFileWithinBudget(\n  candidate: string,\n  budget: RuntimeArtifactScanBudget,\n  maxBytes: number,\n): { bytes: Buffer; state: ReadableFileState } {\n  const before = snapshotReadableFile(candidate);\n  const bytes = stateSize(before.target, candidate);\n  if (budget.bytes + bytes > maxBytes) {\n    throw new Error(`Analyzer runtime scan exceeded ${maxBytes} bytes: ${candidate}`);\n  }\n  const stable = readStableFile(candidate);\n  budget.bytes += stable.bytes.length;\n  return stable;\n}\n\n/** Hash a stable file through a fixed-size buffer instead of materializing it. */","sourceCodeStart":509,"sourceCodeEnd":545,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/aac7515d2a8c50a1f8f923c6fb77218b333560d6/gitnexus/src/core/analyzer-identity.ts#L509-L545","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Re-run the analyze/status command once the concurrent package-manager or watcher process has finished — the error is transient by design.","Serialize the steps: complete 'npm ci'/'npm install' (and any build that rewrites dist/) before invoking gitnexus.","Stop or pause file watchers (tsx watch, nodemon, dev servers) and antivirus/indexing of the gitnexus install directory during the run.","If it persists on an idle filesystem, verify the disk/fs is not corrupt (stat metadata flapping) with 'stat <file>' twice and comparing.","Programmatic callers can wrap resolveAnalyzerRunnerIdentity in a bounded retry (see exampleFix)."],"exampleFix":"// before\nconst identity = resolveAnalyzerRunnerIdentity(import.meta.url);\n\n// after (bounded retry for the transient TOCTOU window)\nfunction resolveStable(url: string, attempts = 3): ReturnType<typeof resolveAnalyzerRunnerIdentity> {\n  for (let i = 0; ; i += 1) {\n    try {\n      return resolveAnalyzerRunnerIdentity(url);\n    } catch (error) {\n      if (i === attempts - 1 || !/^Analyzer identity (directory|input) changed while/.test(String((error as Error).message))) {\n        throw error;\n      }\n    }\n  }\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":"function isIdentityInputChangedError(error: unknown): boolean {\n  return error instanceof Error && /^Analyzer identity input changed while it was being read:/.test(error.message);\n}","tryCatchPattern":"try {\n  identity = resolveAnalyzerRunnerIdentity(import.meta.url);\n} catch (error) {\n  if (!isIdentityInputChangedError(error)) throw error;\n  await sleep(250);\n  identity = resolveAnalyzerRunnerIdentity(import.meta.url); // one bounded retry\n}","preventionTips":["Never run package installs/upgrades concurrently with gitnexus analyze or status.","Exclude the gitnexus install directory from file watchers and antivirus real-time scanning.","In CI, make the install step and the analyze step sequential, not parallel."],"tags":["gitnexus","analyzer-identity","filesystem","toctou","concurrent-modification"],"backgroundTag":"file-modified-during-read","analyzedSha":"aac7515d2a8c50a1f8f923c6fb77218b333560d6","analyzedAt":"2026-08-20T23:29:22.980Z","schemaVersion":2},"datasetVersion":"2026-08-22T20:17:22.307Z"}