abhigyanpatwari/GitNexus · error · Error

${TRUSTED_CACHE_DIRECTORY_ENV} must not traverse symbolic li

Error message

${TRUSTED_CACHE_DIRECTORY_ENV} must not traverse symbolic links or junctions

What it means

Thrown by trustedEnvironmentCacheDirectory when the directory exists and is a real directory, BUT path.resolve(normalized) differs from realpathSync.native(normalized) — meaning some component of the path (an ancestor or the final segment) is a symlink/junction. Even though lstat already rejected a symlink final segment, this second check catches junctions and symlinked ancestors, because the trust assertion must bind to the exact on-disk directory.

Source

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

  }
  const normalized = path.normalize(configured);
  let resolved: string;
  try {
    const link = lstatSync(normalized);
    if (!link.isDirectory() || link.isSymbolicLink()) {
      throw new Error('not a real directory');
    }
    resolved = realpathSync.native(normalized);
  } catch {
    throw new Error(
      `${TRUSTED_CACHE_DIRECTORY_ENV} must name a pre-existing protected non-symlink directory`,
    );
  }
  // Reject junctions/symlinked ancestors as well as a symlink final component.
  // The environment variable is an explicit trust assertion, but its spelling
  // must still bind exactly to the directory the cache will use.
  if (!pathsEqual(path.resolve(normalized), resolved)) {
    throw new Error(`${TRUSTED_CACHE_DIRECTORY_ENV} must not traverse symbolic links or junctions`);
  }
  return resolved;
}

function cacheDirectory(
  options: AnalyzerIdentityResolveOptions,
  packageRoot: string,
  buildRoot: string,
): string | null {
  // An explicit location is a trusted operator/test override and therefore
  // remains authoritative, including when the secure default is unavailable.
  if (options.cacheDirectory) {
    const explicit = path.resolve(options.cacheDirectory);
    try {
      // Create it before any build/dependency directory guards are captured.
      // A cache nested immediately under a package root then changes that
      // parent's directory state once, not after we persist the first entry.
      mkdirSync(explicit, { recursive: true, mode: 0o700 });

View on GitHub (pinned to d540b00184)

Solutions

  1. Point at the realpath directly: run `realpath <configured>` and set the env var to that resolved path.
  2. On macOS prefer `/private/tmp/...` or `$HOME/...` over `/tmp/...`.
  3. On Windows replace junctions with real directories on the target drive.
  4. Re-check after fixing: path.resolve and realpath must agree — verify by comparing the output of `realpath <dir>` against the env var value.

Example fix

# before: /tmp is a symlink to /private/tmp on macOS
#   export GITNEXUS_ANALYZER_IDENTITY_CACHE_DIR=/tmp/gn-id
#   -> "...must not traverse symbolic links or junctions"
#
# after: use the real path
#   export GITNEXUS_ANALYZER_IDENTITY_CACHE_DIR=/private/tmp/gn-id
Defensive patterns

Strategy: validation

Validate before calling

const fs = require('node:fs');
const path = require('node:path');
function validateTrustedCacheNoSymlink() {
  const v = process.env.GITNEXUS_ANALYZER_IDENTITY_CACHE_DIR;
  if (!v) return;
  const normalized = path.normalize(v);
  const resolved = fs.realpathSync.native(normalized);
  const same = process.platform === 'win32'
    ? path.resolve(normalized).toLowerCase() === resolved.toLowerCase()
    : path.resolve(normalized) === resolved;
  if (!same) {
    throw new Error(`${v} resolves to ${resolved}; set the env var to the real path.`);
  }
}
// validateTrustedCacheNoSymlink();

Prevention

When it happens

Trigger: The configured absolute path resolves to a different real path than its lexical form: an ancestor directory is a symlink (e.g. `/tmp` -> `/private/tmp` on macOS, or a `/var` symlink common on some Linux distros), or the path is a junction on Windows. pathsEqual(resolve(normalized), resolved) returns false and the throw fires.

Common situations: macOS `/tmp` (which symlinks to `/private/tmp`) — using `/tmp/gn-id` trips this because `/tmp` is a symlink; `/var` -> `/private/var` similarly; Windows junctions used to redirect a folder onto another drive; a docker volume mount that presents as a symlinked ancestor.

Related errors


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