{"record":{"id":"19c3d43d995f4775","repo":"abhigyanpatwari/GitNexus","slug":"analyzer-build-changed-while-its-identity-was-bein","errorCode":null,"errorMessage":"Analyzer build changed while its identity was being computed: ${buildRoot}","messagePattern":"Analyzer build changed while its identity was being computed: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"gitnexus/src/core/analyzer-identity.ts","lineNumber":2736,"sourceCode":"  const dependencyInputs = collectDependencyInputs(packageRoot, options, traversalLimits);\n  const dependencySnapshotBefore = dependencySnapshot(dependencyInputs);\n  const dependency = hashDependencyRuntime(\n    dependencyInputs,\n    previousCache,\n    options,\n    runtimeVariant,\n  );\n\n  const packageVersion = dependencyInputs.packages[0]?.manifest.version;\n  if (typeof packageVersion !== 'string' || packageVersion.trim() === '') {\n    throw new Error(`GitNexus package version is unavailable in ${packageRoot}`);\n  }\n\n  const buildSnapshotAfter = buildSnapshot(\n    collectBuildEntries(buildRoot, options, traversalLimits),\n  );\n  if (!isDeepStrictEqual(build.snapshot, buildSnapshotAfter)) {\n    throw new Error(`Analyzer build changed while its identity was being computed: ${buildRoot}`);\n  }\n  const dependencySnapshotAfter = dependencySnapshot(\n    collectDependencyInputs(packageRoot, options, traversalLimits),\n  );\n  if (!isDeepStrictEqual(dependencySnapshotBefore, dependencySnapshotAfter)) {\n    throw new Error(\n      `Analyzer dependency runtime changed while its identity was being computed: ${packageRoot}`,\n    );\n  }\n\n  const nextCache: IdentityCachePayload = {\n    schemaVersion: IDENTITY_CACHE_SCHEMA_VERSION,\n    packageRoot,\n    buildRoot,\n    packageVersion,\n    buildKind: kind,\n    buildCanonicalization: BUILD_CANONICALIZATION,\n    dependencyCanonicalization: DEPENDENCY_RUNTIME_CANONICALIZATION,","sourceCodeStart":2718,"sourceCodeEnd":2754,"githubUrl":"https://github.com/abhigyanpatwari/GitNexus/blob/d540b00184d71a896261ee02670da9a92d59d8f7/gitnexus/src/core/analyzer-identity.ts#L2718-L2754","documentation":"Thrown by resolveAnalyzerRunnerIdentity when the build tree's entry snapshot changed between the pre-hash collection (buildSnapshot of the first collectBuildEntries) and a fresh post-hash re-collection. This is a TOCTOU guard: if the build output mutated while the identity was being computed, the digest would not describe a stable artifact, so the resolution aborts rather than persist a misleading receipt.","triggerScenarios":"buildSnapshot(collectBuildEntries(buildRoot,...)) is computed twice — once before hashing build artifacts and once after dependency collection; isDeepStrictEqual(build.snapshot, buildSnapshotAfter) compares them. Any difference in build entries (added/removed/changed files, reordered guards) between the two passes throws, naming buildRoot.","commonSituations":"A concurrent `tsc --watch`, bundler, or test runner writing into the build directory during identity resolution; a build that writes incrementally and was still flushing when the analyzer started; a deploy copying fresh files into buildRoot mid-scan; an IDE indexer touching file mtimes; a container overlayfs applying deferred writes.","solutions":["Quiesce the build tree: stop `tsc --watch`, bundlers, test runners, and any process writing to buildRoot, then retry.","Run the analyzer from a freshly built, frozen artifact: `tsc && node .gitnexus/run.cjs analyze --index-only` in one shot without watch mode.","If using a CI, snapshot the build output into a read-only location and point the analyzer there.","Disable incremental-build file watchers on the analyzer process."],"exampleFix":"// before: analyzer runs concurrently with `tsc --watch` mutating dist/\n//   -> \"Analyzer build changed while its identity was being computed: /repo/dist\"\n//\n// after: build once, stop watchers, then analyze\n//   $ pkill -f 'tsc --watch'\n//   $ tsc\n//   $ node .gitnexus/run.cjs analyze --index-only","handlingStrategy":"retry","validationCode":"const fs = require('node:fs');\nfunction isBuildTreeQuiescent(buildRoot) {\n  // Sample mtimes twice with a small gap; if any changed, the tree is being written to.\n  const snap = () => {\n    const out = {};\n    const walk = (d) => {\n      for (const e of fs.readdirSync(d, { withFileTypes: true })) {\n        const p = require('node:path').join(d, e.name);\n        out[p] = fs.statSync(p).mtimeMs;\n        if (e.isDirectory()) walk(p);\n      }\n    };\n    walk(buildRoot);\n    return out;\n  };\n  const a = snap();\n  require('node:fs').setTimeoutSync?.; // no-op; use Atomics.wait below\n  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 250);\n  const b = snap();\n  for (const k of Object.keys(b)) if (a[k] !== b[k]) return false;\n  return true;\n}\n// if (!isBuildTreeQuiescent(buildRoot)) throw new Error('build tree is being mutated; stop writers before analyze');","typeGuard":null,"tryCatchPattern":"try {\n  identity = resolveAnalyzerRunnerIdentity(analyzerModuleUrl, options);\n} catch (err) {\n  if (err.message.startsWith('Analyzer build changed while its identity was being computed:')) {\n    // TOCTOU race with a concurrent writer; a single retry on a quiescent tree usually succeeds.\n    identity = resolveAnalyzerRunnerIdentity(analyzerModuleUrl, options);\n  } else throw err;\n}","preventionTips":["Stop `tsc --watch`, bundlers, and test runners that write into the build directory before identity resolution.","Run `tsc` (one-shot) then analyze; do not analyze while a watcher is active.","In CI, snapshot the build output into a read-only location and analyze from there.","Treat a repeat failure as a genuine concurrency defect in your build pipeline, not transient."],"tags":["analyzer-identity","toctou","concurrency","build"],"backgroundTag":null,"analyzedSha":"d540b00184d71a896261ee02670da9a92d59d8f7","analyzedAt":"2026-08-12T19:50:25.132Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}