Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

find_codex_home() silently ignores a non-UTF-8 CODEX_HOME and resolves to ~/.codex instead

Open Beginner friendly
#48,124 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
91/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
rust
Domain
cli

Research direction

Start in codex-rs/utils/home-dir/src/lib.rs, especially the CODEX_HOME handling at lines 14-16 and the existing environment-path tests. Run cargo test -p codex-utils-home-dir --test lead_repro -- --test-threads=1, then verify that a non-UTF-8 CODEX_HOME is honored when valid or produces an explicit error, without changing existing UTF-8 behavior.

Written by the indexing model from the issue text.

Description

bug CLI config
What version of Codex CLI is running?

0.155.0 (npm install, linux-x64). Also reproduced against the source the report was
written from; codex-rs/utils/home-dir/src/lib.rs there is byte-identical to
upstream main (55543d8772, 2026-09-25T15:42Z) — blob
caa43569c78bae9f5cc875092f378f4d935b8063 on both sides.

What subscription do you have?

Not applicable — source-level report, no account interaction is involved.

Which model were you using?

Not applicable — the config-resolution layer is exercised before any model call.

What platform is your computer?

uname -mprs → Linux 7.0.0-31-generic x86_64 unknown

What terminal emulator and version are you using?

Not applicable; non-interactive codex doctor --json.

Codex doctor report

Relevant excerpt is inline below; the remainder is omitted. No secrets are involved.


What issue are you seeing?

find_codex_home() discards CODEX_HOME when its value is not valid UTF-8, and
silently resolves to ~/.codex instead: no error, no warning, and codex doctor
reports config.load: ok.

CODEX_HOME is read with std::env::var, which returns
Err(VarError::NotUnicode) for a non-UTF-8 value. .ok() converts that error
into None, which is indistinguishable from "variable not set", so
find_codex_home_from_env takes the default branch.

Non-UTF-8 path names are legal on Linux and macOS (they routinely appear when
extracting archives with non-UTF-8 names, or from filesystems with mixed
encodings), so this is a real, if uncommon, configuration.

Because this resolver backs config, credentials and session state, the
consequence is that an explicitly requested directory is ignored and a
different one is used — silently.

Observed with a sentinel model value written into both candidate configs, so
the file that was actually loaded is unambiguous:

CODEX_HOME config file actually loaded sentinel model
unset $HOME/.codex/config.toml FALLBACK-home-codex
UTF-8 path, exists <that dir>/config.toml REQUESTED-utf8-home
non-UTF-8 path, exists $HOME/.codex/config.toml FALLBACK-home-codex

codex doctor --json for the non-UTF-8 case — note status=ok, and that the
requested directory is not mentioned anywhere:

[config.load] status=ok summary='config loaded'
    CODEX_HOME  = '/tmp/repro/home/.codex'
    config.toml = '/tmp/repro/home/.codex/config.toml'
    model       = 'FALLBACK-home-codex'
[state.paths] status=ok summary='state paths and databases are inspectable'
    CODEX_HOME  = '/tmp/repro/home/.codex (dir)'

The requested directory /tmp/repro/non-utf8-\xff existed throughout and
contained a valid config.toml with model = "REQUESTED-nonutf8-home".

What steps can reproduce the bug?

Invoke the native binary directly. ($HOME is redirected so that the
fallback target is a scratch directory; nothing outside the temp tree is
read or written.)

import os, subprocess, tempfile

NATIVE = ("<install root>/node_modules/@openai/codex-linux-x64"
          "/vendor/x86_64-unknown-linux-musl/bin/codex")

scratch = tempfile.mkdtemp(prefix="repro-")
home = os.path.join(scratch, "home")
os.makedirs(os.path.join(home, ".codex"))
with open(os.path.join(home, ".codex", "config.toml"), "w") as f:
    f.write('model = "FALLBACK-home-codex"\n')

requested = os.fsencode(scratch) + b"/non-utf8-\xff"   # not valid UTF-8
os.mkdir(requested)
with open(os.path.join(requested, b"config.toml"), "wb") as f:
    f.write(b'model = "REQUESTED-nonutf8-home"\n')

env = dict(os.environ, HOME=home, CODEX_HOME=os.fsdecode(requested))
out = subprocess.run([NATIVE, "doctor", "--json"], capture_output=True,
                     env=env, cwd=scratch).stdout.decode("utf-8", "replace")
print("requested:", requested)
for line in out.splitlines():
    if "config.toml" in line or '"model"' in line:
        print("  ", line.strip())

Observed output:

requested: b'/tmp/repro-xxxxxxxx/non-utf8-\xff'
   "config.toml": "/tmp/repro-xxxxxxxx/home/.codex/config.toml",
   "model": "FALLBACK-home-codex",

The requested directory is ignored. With a UTF-8 name in the same position, the
requested directory is honored.

The same behavior can be reproduced at unit level in
codex-rs/utils/home-dir, where the resolver lives:

let mut raw = base.into_os_string().into_vec();
raw.extend_from_slice(b"/codex-home-\xff");
let dir = std::path::PathBuf::from(std::ffi::OsString::from_vec(raw));
std::fs::create_dir_all(&dir).unwrap();
unsafe { std::env::set_var("CODEX_HOME", dir.as_os_str()) }; // edition 2024
assert_eq!(
    codex_utils_home_dir::find_codex_home().unwrap().as_path(),
    dir.canonicalize().unwrap(),
); // fails: resolved to $HOME/.codex
$ cargo test -p codex-utils-home-dir --test lead_repro -- --test-threads=1
control_utf8_codex_home_is_honored ... ok
subject_non_utf8_codex_home_existing_dir_is_ignored ... FAILED
  left:  "/tmp/repro-xxxxxxxx/home/.codex"
 right: "/tmp/repro-xxxxxxxx/codex-home-\xFF"
What is the expected behavior?

The function's own documentation (codex-rs/utils/home-dir/src/lib.rs:5-12)
states the contract:

  • If CODEX_HOME is set, the value must exist and be a directory. The value
    will be canonicalized and this function will Err otherwise.

A non-UTF-8 value therefore has two acceptable outcomes, and the current
behavior implements neither: resolve to the directory when it exists and is a
directory, or return an error naming CODEX_HOME. Silently substituting
~/.codex is the one outcome the documented contract rules out. The crate's
existing tests encode the same intent for UTF-8 values
(find_codex_home_env_missing_path_is_fatal,
find_codex_home_env_file_path_is_fatal).

This also defeats the validation added for CODEX_HOME in #10249 ("Validate
CODEX_HOME before resolving"), which is the commit that introduced the
env::var(...).ok() chain — so the fail-loudly guarantee added there does not
hold for this input class.

Additional information

Root cause. codex-rs/utils/home-dir/src/lib.rs:14-16:

let codex_home_env = std::env::var("CODEX_HOME")
    .ok()
    .filter(|val| !val.is_empty());

VarError::NotUnicode is collapsed into None by .ok(), so the Some(val)
branch at :24, including the "path does not exist" error at :29, is never
reached for such values.

Possible fix. Read with std::env::var_os("CODEX_HOME") and keep the
empty-value filter; pass the OsString/PathBuf through so that non-UTF-8
directories resolve normally, or return an explicit error when the value is not
representable. Other CODEX_HOME readers in the tree already use var_os —
config/src/codex_home_symlink.rs:23 and cli/src/doctor/disk.rs:19
(path-utils/src/env.rs:7 does the same for WSL_DISTRO_NAME) — so this read
is the outlier.

Blast radius. find_codex_home() has 47 non-test call sites, including core
config loading, rmcp-client OAuth storage and refresh locks, network-proxy
CA storage, session/rollout stores, sandboxing and arg0. The requested home is
not merely displayed incorrectly; it is not used.

Launcher note (separate from the defect above). With the npm launcher the
symptom looks different, because the value is mangled before codex starts:
/usr/local/bin/codex is a Node script that does const env = { ...process.env }
and then spawn(binaryPath, argv, { env }). Node decodes environment values as
UTF-8, so 0xFF becomes U+FFFD and the child receives a different,
non-existent path, producing:

CODEX_HOME points to "...non-utf8-\uFFFD", but that path does not exist

That loud error is why the silent fallback is easy to miss for npm users. The
fallback itself is observable whenever the native binary is invoked directly, or
by any launcher that preserves the bytes.

Not a security report. No sandbox, permission or approval bypass, and no
attacker-controlled input. It is worth a look only because the auth store is
among the directories that can be silently relocated.

Duplicate search. Searched open and closed issues for CODEX_HOME,
CODEX_HOME non-UTF-8, NotUnicode, find_codex_home, invalid unicode environment variable and CODEX_HOME ignored. Nothing covers this mechanism.
The closest existing reports are #45871 (find_codex_home()'s canonicalize()
denied inside a Windows AppContainer — same function and the same class of "a
valid CODEX_HOME is not honored", different mechanism), #46787 and #35310
(symlinked CODEX_HOME), and #27765 (deep CODEX_HOME path).

Verified on Linux only. Windows environment values are UTF-16; an unpaired
surrogate would take the same NotUnicode path, but I could not test that here.

Dominant language
Rust
Stars
125k
Forks
19.5k
Avg merge
1m
Merged PRs (30d)
996

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from openai/codex

All issues in openai/codex

Similar issues

More Rust issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.