abhigyanpatwari/GitNexus · error

must name a pre-existing protected non-symlink directory

Error message

${TRUSTED_CACHE_DIRECTORY_ENV} must name a pre-existing protected non-symlink directory

What it means

After passing the absolute-path check, the trusted cache directory must be an already-existing real directory: lstat must succeed, report a directory, and not a symbolic link, and realpathSync.native must resolve. Any miss (missing directory, symlink final component, file instead of directory, permission error surfaced as a thrown syscall error) is collapsed into this error. The cache directory must be pre-created by the operator because the code intentionally does not mkdir an env-declared trusted path.

Solutions

  1. Create the directory before running: mkdir -p /var/cache/gitnexus-identity && export GITNEXUS_ANALYZER_IDENTITY_CACHE_DIR=/var/cache/gitnexus-identity.
  2. If the path is a symlink, point the variable directly at the real target (on macOS use /private/tmp not /tmp).
  3. Verify it is a plain directory: [ -d "$DIR" ] && [ ! -L "$DIR" ] && echo ok.
  4. Fix ownership/permissions so the process can lstat and realpath it.

Example fix

# before
export GITNEXUS_ANALYZER_IDENTITY_CACHE_DIR=/tmp/gnx-id-cache
npx gitnexus analyze
# ...must name a pre-existing protected non-symlink directory

# after (note: /tmp is a symlink on macOS; create a real dir first)
mkdir -p "$HOME/.cache/gnx-id-cache"
export GITNEXUS_ANALYZER_IDENTITY_CACHE_DIR="$HOME/.cache/gnx-id-cache"
npx gitnexus analyze
Defensive patterns

Strategy: validation

Validate before calling

// Pre-flight the trusted cache dir exactly the way the tool does:
import { lstatSync, realpathSync } from 'node:fs';
import { isAbsolute } from 'node:path';
function trustedCacheDirOk(dir: string): boolean {
  if (!dir || dir.includes('\0') || !isAbsolute(dir)) return false;
  try {
    const s = lstatSync(dir);
    return s.isDirectory() && !s.isSymbolicLink();
  } catch {
    return false; // must pre-exist; the tool will not create it
  }
}

Try / catch

try {
  spawnSync('gitnexus', ['analyze']);
} catch (err) {
  if (String((err as Error).message).includes('pre-existing protected non-symlink directory')) {
    mkdirSync(process.env.GITNEXUS_ANALYZER_IDENTITY_CACHE_DIR!, { recursive: true }); // then point at the real path
    process.env.GITNEXUS_ANALYZER_IDENTITY_CACHE_DIR = realpathSync(process.env.GITNEXUS_ANALYZER_IDENTITY_CACHE_DIR!);
    return spawnSync('gitnexus', ['analyze']);
  }
  throw err;
}

Prevention

When it happens

Trigger: GITNEXUS_ANALYZER_IDENTITY_CACHE_DIR points at a directory that does not exist yet, points at a symlink (ln -s), points at a regular file, or is unreadable due to permissions — then trustedEnvironmentCacheDirectory() lstat/realpath throws and the error is rethrown with the guidance message.

Common situations: Operators expecting the tool to create the cache dir (common with XDG-style conventions), pointing at /tmp symlinks on macOS (/tmp -> /private/tmp), Docker volume mounts that arrive as symlinks, or a typo'd path that was never mkdir'd.

Understand the failure class

Background: "is not a valid" / "Invalid ... value" environment variable errors: how libraries validate env vars and what to do when they reject yours — this error's family across 48 libraries.

Related errors


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

Appendix: source

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

  return process.platform === 'win32' ? left.toLowerCase() === right.toLowerCase() : left === right;
}

function trustedEnvironmentCacheDirectory(): string | null {
  const configured = process.env[TRUSTED_CACHE_DIRECTORY_ENV];
  if (configured === undefined) return null;
  if (configured.length === 0 || configured.includes('\0') || !path.isAbsolute(configured)) {
    throw new Error(`${TRUSTED_CACHE_DIRECTORY_ENV} must name an absolute protected directory`);
  }
  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

View on GitHub (pinned to 52924ef12c)