JuliusBrussee/caveman · error · Error

failed to read CODEX_HOME

Error message

failed to read CODEX_HOME ${JSON.stringify(configured)}: ${(error as Error).message}

What it means

Thrown when resolving CODEX_HOME fails with a filesystem error other than ENOENT (e.g. EACCES, ENOTDIR). The CLI stats the configured CODEX_HOME path before use and wraps any non-missing-file read failure into this error, including the OS error message. It means the path exists conceptually but could not be stat'ed.

Solutions

  1. Verify the CODEX_HOME path: run `ls -la` on it and each ancestor to find the inaccessible or wrong component
  2. Fix the path in the environment (unset CODEX_HOME or point it at an existing writable directory)
  3. Correct permissions on ancestor directories (chmod +x on directories along the path)
  4. Re-run the command; if it is a transient mount/I/O problem, retry after the filesystem is healthy

Example fix

// before
CODEX_HOME=/home/user/notes.txt codex ...
// after
CODEX_HOME=/home/user/.codex codex ...
Defensive patterns

Strategy: try-catch

Validate before calling

const configured = process.env.CODEX_HOME;
if (configured) {
  try { const st = fs.statSync(configured); if (!st.isDirectory()) throw new Error(`${configured} is not a directory`); }
  catch (e) { console.error(`Cannot access CODEX_HOME ${configured}: ${e.message}`); }
}

Type guard

function isAccessibleDir(p: string): boolean {
  try { return fs.statSync(p).isDirectory(); } catch { return false; }
}

Try / catch

try {
  runCodexCommand();
} catch (e) {
  if (String(e.message).startsWith("failed to read CODEX_HOME")) {
    console.error(`CODEX_HOME is set but unreadable: ${e.message}. Fix the path or unset CODEX_HOME.`);
  }
}

Prevention

When it happens

Trigger: statSync(configured) on the CODEX_HOME path throws an ErrnoException whose code is not ENOENT — e.g. a parent directory component is not a directory (ENOTDIR), permission is denied (EACCES), or an I/O error occurs on the path.

Common situations: CODEX_HOME pointing inside a file path (e.g. /home/user/file.txt/subdir), a symlink with no search permission on an ancestor directory, a network mount being unavailable, or running as a user lacking execute permission on a parent directory.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@3ee70a1026 (2026-09-20). Data as JSON: /api/errors/7fec6bb154cb7bc1. Report an issue: GitHub.

Appendix: source

Thrown at packages/cli/src/index.ts:6200

  if (!prefix || !path.startsWith(prefix)) return path;
  const root = agent === "claude" ? claudeConfigDir() : codexHomeDir();
  return join(root, path.slice(prefix.length));
}

export function codexHomeDir(env: NodeJS.ProcessEnv = process.env): string {
  const configured = env.CODEX_HOME;
  if (!configured) return join(homedir(), ".codex");
  // Match Codex's own home resolver: a nonempty override may be relative, but
  // must already be a directory and is canonicalized before use. Never fall
  // back to another account's default home when the override is invalid.
  let metadata: ReturnType<typeof statSync>;
  try {
    metadata = statSync(configured);
  } catch (error) {
    if ((error as NodeJS.ErrnoException).code === "ENOENT") {
      throw new Error(`CODEX_HOME points to ${JSON.stringify(configured)}, but that path does not exist`);
    }
    throw new Error(`failed to read CODEX_HOME ${JSON.stringify(configured)}: ${(error as Error).message}`);
  }
  if (!metadata.isDirectory()) {
    throw new Error(`CODEX_HOME points to ${JSON.stringify(configured)}, but that path is not a directory`);
  }
  try {
    return realpathSync(configured);
  } catch (error) {
    throw new Error(`failed to canonicalize CODEX_HOME ${JSON.stringify(configured)}: ${(error as Error).message}`);
  }
}

function codexAuthPath(): string {
  return join(codexHomeDir(), "auth.json");
}

function nonEmptyString(v: unknown): v is string {
  return typeof v === "string" && v.trim().length > 0;
}

View on GitHub (pinned to 3ee70a1026)