JuliusBrussee/caveman · error · Error

failed to canonicalize CODEX_HOME

Error message

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

What it means

Thrown when realpathSync fails while canonicalizing an existing CODEX_HOME directory (it passed the stat and isDirectory checks). This resolves symlinks to an absolute canonical path; failure here usually means the path became inaccessible between stat and realpath, or a symlink loop / permission issue on a symlinked component.

Solutions

  1. Re-check the path exists and is traversable: `ls -la` on CODEX_HOME and ancestors
  2. Fix symlink loops: inspect with `namei -l "$CODEX_HOME"` and remove the cyclic link
  3. Restore read/execute permissions on symlinked directories in the path
  4. Retry; if the directory was concurrently deleted, recreate it with mkdir -p

Example fix

// before (symlink loop)
ln -s .codex .codex/loop
// after
rm .codex/loop && mkdir -p "$CODEX_HOME"
Defensive patterns

Strategy: try-catch

Validate before calling

try { fs.realpathSync(process.env.CODEX_HOME ?? ""); } catch (e) { console.error(`CODEX_HOME cannot be canonicalized: ${e.message}`); }

Type guard

function isRealCanonicalizableDir(p: string): boolean {
  try { return fs.statSync(p).isDirectory() && !!fs.realpathSync(p); } catch { return false; }
}

Try / catch

try {
  runCodexCommand();
} catch (e) {
  if (String(e.message).startsWith("failed to canonicalize CODEX_HOME")) {
    console.error("Check CODEX_HOME for symlink loops or permission changes: " + e.message);
  }
}

Prevention

When it happens

Trigger: realpathSync(configured) throws — typically EACCES on a symlink component, ELOOP from a symlink cycle, or ENOENT if the path was deleted between stat and realpath.

Common situations: CODEX_HOME containing a self-referencing symlink loop, permissions tightened on ~/.codex or an ancestor after stat, or the directory being concurrently removed.

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/5cd791b6408231d5. Report an issue: GitHub.

Appendix: source

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

  // 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;
}

function readCodexAuthJson(): JsonObject | undefined {
  const path = codexAuthPath();
  try {
    const stat = statSync(path);
    if (!stat.isFile() || stat.size === 0) return undefined;
    const parsed = JSON.parse(readFileSync(path, "utf8")) as unknown;
    return asJsonObject(parsed);

View on GitHub (pinned to 3ee70a1026)