abhigyanpatwari/GitNexus · error · Error

Unable to read eval-server authentication from

Error message

Unable to read eval-server authentication from ${filePath}

What it means

While resolving the eval-server bearer token, readAuthTokenFile reads .env.local / .env and lets only ENOENT pass silently (a missing file is fine). Any other failure — EACCES, EISDIR, or the env parser choking on the content — is rethrown wrapped, with the original error attached as `cause`. The message names the exact file path it could not read.

Solutions

  1. Check the path named in the message: ls -la <filePath> and confirm it is a readable regular file
  2. Fix ownership/permissions: chmod 644 (or chown to the running user) — only .env.local/.env, never secret-wide loosening
  3. Inspect (error as any).cause for the underlying errno to distinguish EACCES vs EISDIR vs parse failure
  4. If the file is genuinely unwanted, remove it — absence is handled gracefully

Example fix

# before
$ ls -la .env.local  # -rw------- root root, server runs as node
# after
$ chown node:node .env.local && chmod 600 .env.local
Defensive patterns

Strategy: try-catch

Validate before calling

import { accessSync, constants } from 'node:fs';
for (const p of ['.env.local', '.env']) {
  try {
    accessSync(p, constants.R_OK);
  } catch {
    if (require('node:fs').existsSync(p)) {
      throw new Error(`${p} exists but is not readable — fix ownership/mode`);
    }
  }
}

Try / catch

try {
  resolveEvalServerAuthToken(process.env, cwd);
} catch (err) {
  const cause = (err as Error & { cause?: Error }).cause;
  const code = (cause as NodeJS.ErrnoException | undefined)?.code;
  if (code === 'EACCES') { /* chmod/chown the file named in the message */ }
  else if (code === 'EISDIR') { /* .env.local is a directory — remove it */ }
  else throw err;
}

Prevention

When it happens

Trigger: .env.local or .env with restrictive permissions (chmod 600 owned by another user), the path existing as a directory, or content the env parser rejects; resolveEvalServerAuthToken(env, cwd) then throws instead of falling through to the next source.

Common situations: Docker runs where the env file was COPY'd with root ownership but the server runs as a non-root uid; shared machines; CI copying env files with 600 from a different user; a mounted secret directory at the .env path.

Understand the failure class

Related errors


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

Appendix: source

Thrown at gitnexus/src/cli/eval-server.ts:107

): Promise<string | null> {
  const directHost = validateHost(raw);
  if (directHost && directHost !== 'localhost') return directHost;
  if (directHost !== 'localhost' && !isHostname(raw)) return null;

  try {
    const address = await resolveHostname(raw);
    return isIPv4(address) ? address : null;
  } catch {
    return null;
  }
}

function readAuthTokenFile(filePath: string): string | undefined {
  try {
    return parseEnv(readFileSync(filePath, 'utf8')).GITNEXUS_AUTH_TOKEN?.trim() || undefined;
  } catch (error) {
    if ((error as NodeJS.ErrnoException).code === 'ENOENT') return undefined;
    throw new Error(`Unable to read eval-server authentication from ${filePath}`, { cause: error });
  }
}

/** Resolve the bearer token from the shell, then .env.local, then .env. */
export function resolveEvalServerAuthToken(
  env: NodeJS.ProcessEnv,
  cwd: string = process.cwd(),
): string | undefined {
  if (Object.hasOwn(env, 'GITNEXUS_AUTH_TOKEN')) {
    return env.GITNEXUS_AUTH_TOKEN?.trim() || undefined;
  }

  return (
    readAuthTokenFile(path.join(cwd, '.env.local')) ?? readAuthTokenFile(path.join(cwd, '.env'))
  );
}

/** True only for literal loopback addresses; DNS names are resolved before this check. */

View on GitHub (pinned to ac9a4e9abd)