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
- Create the directory before running: mkdir -p /var/cache/gitnexus-identity && export GITNEXUS_ANALYZER_IDENTITY_CACHE_DIR=/var/cache/gitnexus-identity.
- If the path is a symlink, point the variable directly at the real target (on macOS use /private/tmp not /tmp).
- Verify it is a plain directory: [ -d "$DIR" ] && [ ! -L "$DIR" ] && echo ok.
- 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
- Provision the cache directory in your setup script (mkdir -p) before first run; the env path is never auto-created.
- Never point the variable at a symlink or a macOS /tmp alias path.
- Include the directory creation in Dockerfiles and CI bootstrap steps.
- Prefer $HOME- or /var/cache-based paths that are stable and real.
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
- must be outside the analyzer package and build roots
- must name an absolute protected directory
- must not traverse symbolic links or junctions
- Analyzer build symbolic links are not supported…
- Analyzer package lock symbolic link does not resolve to a…
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 thereforeView on GitHub (pinned to 52924ef12c)