abhigyanpatwari/GitNexus · error

Analyzer vendored runtime directory is unavailable

Error message

Analyzer vendored runtime directory is unavailable: ${vendorRoot}

What it means

collectVendoredGrammarInputs first checks the vendor/ directory exists, then calls recordDirectoryGuard(vendorRoot) to register a tamper guard on it; if that registration returns false the vendored runtime directory is considered unavailable/unstable and the error is thrown. It protects against the vendor directory appearing then disappearing or being unstatable between the two checks (TOCTOU), which would otherwise let an unguarded directory mutate after identity computation.

Solutions

  1. Verify the directory is readable and stable: ls -la <packageRoot>/vendor run twice; fix permissions if the second stat fails (chmod/chown on the package root).
  2. Stop concurrent installs/sync tools touching the tree and re-run analyze — the guard is racing a mutation.
  3. Move the GitNexus install off network/ephemeral storage onto a local filesystem.
  4. If vendor/ is not actually needed (optional grammars), reinstall the package so the tree is consistent rather than half-materialized.

Example fix

# before
$ npx gitnexus analyze
# Analyzer vendored runtime directory is unavailable: /usr/lib/node_modules/gitnexus/vendor

# after: fix permissions and stop the racing sync client
sudo chown -R "$USER" /usr/lib/node_modules/gitnexus
killall Dropbox 2>/dev/null || true
npx gitnexus analyze
Defensive patterns

Strategy: retry

Validate before calling

// Verify the vendored grammar directory is stably readable before analyze:
import { lstatSync } from 'node:fs';
function vendorRootStable(packageRoot: string): boolean {
  const vendorRoot = join(packageRoot, 'vendor');
  for (let i = 0; i < 3; i++) {
    try {
      const s = lstatSync(vendorRoot);
      if (!s.isDirectory()) return false;
    } catch {
      return false;
    }
  }
  return true;
}

Try / catch

try {
  await resolveAnalyzerRunnerIdentity(moduleUrl);
} catch (err) {
  if (err instanceof Error && err.message.includes('vendored runtime directory is unavailable')) {
    await stopSyncToolsAndConcurrentInstalls(); // then retry once
    return resolveAnalyzerRunnerIdentity(moduleUrl);
  }
  throw err;
}

Prevention

When it happens

Trigger: resolveAnalyzerRunnerIdentity() on a package whose <packageRoot>/vendor exists and is a directory, but recordDirectoryGuard(vendorRoot) fails — typically because lstat/stat on the directory errors (permission denied, disappearing mid-scan, NFS/ephemeral mounts) after existsSync already passed.

Common situations: vendor/ mounted on a flaky network filesystem, an antivirus or sync client (Dropbox/OneDrive) locking the directory, a concurrent npm install mutating the tree, or permission changes on the directory between the two syscalls.

Related errors


AI-assisted analysis of abhigyanpatwari/GitNexus@52924ef12c (2026-08-20). Data as JSON: /api/errors/494b1e226613111e. Report an issue: GitHub.

Appendix: source

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

  packageRoot: string,
  directoryGuards: Map<string, DependencyDirectoryGuard>,
  options: AnalyzerIdentityResolveOptions,
  budget: RuntimeArtifactScanBudget,
  limits: AnalyzerIdentityTraversalLimits,
): {
  manifests: DependencyInputs['vendoredManifests'];
  artifacts: RuntimeArtifact[];
} {
  const vendorRoot = path.join(packageRoot, 'vendor');
  if (!existsSync(vendorRoot) || !lstatSync(vendorRoot).isDirectory()) {
    recordDirectoryGuard(directoryGuards, packageRoot);
    return { manifests: [], artifacts: [] };
  }

  const manifests: DependencyInputs['vendoredManifests'] = [];
  const artifacts: RuntimeArtifact[] = [];
  if (!recordDirectoryGuard(directoryGuards, vendorRoot)) {
    throw new Error(`Analyzer vendored runtime directory is unavailable: ${vendorRoot}`);
  }
  const vendorEntries = readDirectory(vendorRoot, options);
  budget.entries += vendorEntries.length;
  if (budget.entries > limits.runtimeEntries) {
    throw new Error(
      `Analyzer runtime payload scan exceeded ${limits.runtimeEntries} entries: ${vendorRoot}`,
    );
  }
  for (const entry of vendorEntries) {
    if (!entry.isDirectory() || !entry.name.startsWith('tree-sitter-')) continue;
    const grammarRoot = path.join(vendorRoot, entry.name);
    const manifestPath = path.join(grammarRoot, 'package.json');
    if (isFile(manifestPath)) {
      options.onCacheMissWork?.({ kind: 'manifest-read', path: manifestPath });
      const read = readStableFileWithinBudget(manifestPath, budget, limits.runtimeBytes);
      manifests.push({
        canonicalPath: `vendor:${entry.name}/package.json`,
        absolutePath: manifestPath,

View on GitHub (pinned to 52924ef12c)