{"record":{"id":"0e6da692145431ff","repo":"abhigyanpatwari/GitNexus","slug":"analyzer-identity-input-changed-while-it-was-being-0e6da6","errorCode":null,"errorMessage":"Analyzer identity input changed while it was being hashed: ${candidate}","messagePattern":"Analyzer identity input changed while it was being hashed: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"gitnexus/src/core/analyzer-identity.ts","lineNumber":585,"sourceCode":"        bytes += read;\n        if (bytes > expectedBytes) break;\n      }\n      const openedAfter = statState(fstatSync(descriptor, { bigint: true }));\n      closeSync(descriptor);\n      descriptor = null;\n      const after = snapshotReadableFile(candidate);\n      if (\n        bytes === expectedBytes &&\n        isDeepStrictEqual(openedBefore, openedAfter) &&\n        isDeepStrictEqual(before, after)\n      ) {\n        return { digest: `sha256:${hash.digest('hex')}`, state: after, bytes };\n      }\n    } finally {\n      if (descriptor !== null) closeSync(descriptor);\n    }\n  }\n  throw new Error(`Analyzer identity input changed while it was being hashed: ${candidate}`);\n}\n\nfunction resolveExistingPath(candidate: string): string {\n  return realpathSync.native(path.resolve(candidate));\n}\n\n/**\n * Case-stabilize a path's Windows drive letter so two processes that observed\n * the same directory under different drive-letter casing (`c:\\…` vs `C:\\…`)\n * produce byte-identical analyzer-identity path fields (#2668).\n *\n * `realpathSync.native` canonicalizes 8.3 short names and symlinks but does not\n * guarantee the drive-letter case it returns — it can preserve whatever casing\n * the caller's path carried, and `import.meta.url` casing depends on how each\n * entry process (CLI shim vs `npx`/npm wrapper vs server worker) was launched.\n * When `analyze` stamps `build.rootPath` under one casing and `status`\n * recomputes it under another, `analyzerRunnerIdentitiesEqual` deep-compares\n * unequal and `status` reports a freshly-analyzed, untouched repo as stale.","sourceCodeStart":567,"sourceCodeEnd":603,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/aac7515d2a8c50a1f8f923c6fb77218b333560d6/gitnexus/src/core/analyzer-identity.ts#L567-L603","documentation":"hashStableFile() streams a file through a 256 KiB buffer, hashing as it goes, and validates that the stat snapshot taken before the open, the fstat of the opened descriptor, the byte count actually read, and a fresh stat after reading all agree; after two failed attempts it declares the input unstable. The receipt hash must reflect exact bytes, so a file mutated while being hashed (changed size, mtime, or inode) is rejected rather than hashed incorrectly.","triggerScenarios":"Reached for every build-tree file and hashed runtime artifact during resolveAnalyzerRunnerIdentity — e.g. 'gitnexus analyze' hashing dist/ while a bundler/watcher (tsc --watch, esbuild, vite) rewrites a .js/.wasm/.node file, or an install/upgrade process replaces files under node_modules or dist mid-scan.","commonSituations":"Running gitnexus in one terminal while 'npm run build'/'npm install' runs in another; Docker volume mounts with lazy materialization mutating mtimes; synced folders (Dropbox/OneDrive) downloading files during the scan; antivirus rewriting files after inspection on Windows.","solutions":["Re-run analyze after builds, installs, and sync tools are quiescent.","Sequence CI steps so 'gitnexus analyze' runs strictly after the install/build step, not in parallel.","Exclude the gitnexus install and dist directories from cloud-sync folders and real-time antivirus scanning.","For programmatic use, retry the resolve a bounded number of times (same pattern as error 40)."],"exampleFix":"// before\nconst identity = resolveAnalyzerRunnerIdentity(import.meta.url);\n\n// after\nasync function resolveWithRetry(url: string, tries = 3) {\n  for (let i = 0; ; i += 1) {\n    try {\n      return resolveAnalyzerRunnerIdentity(url);\n    } catch (error) {\n      const message = String((error as Error).message);\n      if (i === tries - 1 || !/changed while (it was being|its identity was being)/.test(message)) throw error;\n      await new Promise((resolve) => setTimeout(resolve, 250 * (i + 1)));\n    }\n  }\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":"function isIdentityHashChangedError(error: unknown): boolean {\n  return error instanceof Error && /^Analyzer identity input changed while it was being hashed:/.test(error.message);\n}","tryCatchPattern":"for (let attempt = 0; attempt < 3; attempt += 1) {\n  try {\n    identity = resolveAnalyzerRunnerIdentity(import.meta.url);\n    break;\n  } catch (error) {\n    if (!isIdentityHashChangedError(error) || attempt === 2) throw error;\n    await sleep(250 * (attempt + 1));\n  }\n}","preventionTips":["Finish builds (tsc/esbuild/vite) before running analyze; stop watch mode first.","Keep the gitnexus dist/ out of cloud-sync folders.","Serialize CI stages so analyze never overlaps the stage that writes dist or node_modules."],"tags":["gitnexus","analyzer-identity","filesystem","toctou","hashing"],"backgroundTag":"file-modified-during-read","analyzedSha":"aac7515d2a8c50a1f8f923c6fb77218b333560d6","analyzedAt":"2026-08-20T23:29:22.980Z","schemaVersion":2},"datasetVersion":"2026-08-22T20:17:22.307Z"}