abhigyanpatwari/GitNexus · error · Error

Analyzer build changed while its identity was being computed

Error message

Analyzer build changed while its identity was being computed: ${buildRoot}

What it means

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.

Source

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

  const dependencyInputs = collectDependencyInputs(packageRoot, options, traversalLimits);
  const dependencySnapshotBefore = dependencySnapshot(dependencyInputs);
  const dependency = hashDependencyRuntime(
    dependencyInputs,
    previousCache,
    options,
    runtimeVariant,
  );

  const packageVersion = dependencyInputs.packages[0]?.manifest.version;
  if (typeof packageVersion !== 'string' || packageVersion.trim() === '') {
    throw new Error(`GitNexus package version is unavailable in ${packageRoot}`);
  }

  const buildSnapshotAfter = buildSnapshot(
    collectBuildEntries(buildRoot, options, traversalLimits),
  );
  if (!isDeepStrictEqual(build.snapshot, buildSnapshotAfter)) {
    throw new Error(`Analyzer build changed while its identity was being computed: ${buildRoot}`);
  }
  const dependencySnapshotAfter = dependencySnapshot(
    collectDependencyInputs(packageRoot, options, traversalLimits),
  );
  if (!isDeepStrictEqual(dependencySnapshotBefore, dependencySnapshotAfter)) {
    throw new Error(
      `Analyzer dependency runtime changed while its identity was being computed: ${packageRoot}`,
    );
  }

  const nextCache: IdentityCachePayload = {
    schemaVersion: IDENTITY_CACHE_SCHEMA_VERSION,
    packageRoot,
    buildRoot,
    packageVersion,
    buildKind: kind,
    buildCanonicalization: BUILD_CANONICALIZATION,
    dependencyCanonicalization: DEPENDENCY_RUNTIME_CANONICALIZATION,

View on GitHub (pinned to d540b00184)

Solutions

  1. Quiesce the build tree: stop `tsc --watch`, bundlers, test runners, and any process writing to buildRoot, then retry.
  2. Run the analyzer from a freshly built, frozen artifact: `tsc && node .gitnexus/run.cjs analyze --index-only` in one shot without watch mode.
  3. If using a CI, snapshot the build output into a read-only location and point the analyzer there.
  4. Disable incremental-build file watchers on the analyzer process.

Example fix

// before: analyzer runs concurrently with `tsc --watch` mutating dist/
//   -> "Analyzer build changed while its identity was being computed: /repo/dist"
//
// after: build once, stop watchers, then analyze
//   $ pkill -f 'tsc --watch'
//   $ tsc
//   $ node .gitnexus/run.cjs analyze --index-only
Defensive patterns

Strategy: retry

Validate before calling

const fs = require('node:fs');
function isBuildTreeQuiescent(buildRoot) {
  // Sample mtimes twice with a small gap; if any changed, the tree is being written to.
  const snap = () => {
    const out = {};
    const walk = (d) => {
      for (const e of fs.readdirSync(d, { withFileTypes: true })) {
        const p = require('node:path').join(d, e.name);
        out[p] = fs.statSync(p).mtimeMs;
        if (e.isDirectory()) walk(p);
      }
    };
    walk(buildRoot);
    return out;
  };
  const a = snap();
  require('node:fs').setTimeoutSync?.; // no-op; use Atomics.wait below
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 250);
  const b = snap();
  for (const k of Object.keys(b)) if (a[k] !== b[k]) return false;
  return true;
}
// if (!isBuildTreeQuiescent(buildRoot)) throw new Error('build tree is being mutated; stop writers before analyze');

Try / catch

try {
  identity = resolveAnalyzerRunnerIdentity(analyzerModuleUrl, options);
} catch (err) {
  if (err.message.startsWith('Analyzer build changed while its identity was being computed:')) {
    // TOCTOU race with a concurrent writer; a single retry on a quiescent tree usually succeeds.
    identity = resolveAnalyzerRunnerIdentity(analyzerModuleUrl, options);
  } else throw err;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of abhigyanpatwari/GitNexus@d540b00184 (2026-08-12). Data as JSON: /api/errors/19c3d43d995f4775. Report an issue: GitHub.