macOS Codex global-state atomic writer recreates private state files as 0644

Open 💬 0 comments Opened Jul 30, 2026 by Sergei-Koval

Summary

On macOS, the Codex desktop app repeatedly recreates
$CODEX_HOME/.codex-global-state.json and its .bak file as 0644. A manual
chmod 0600 is not persistent because the next normal state save atomically
replaces both paths.

Environment

  • Codex app 26.721.81911, build 5973
  • bundled CLI 0.146.0-alpha.3.1
  • macOS 27.0 build 26A5388g, arm64
  • observed umask 0022

Reproduction

  1. With Codex running, record the mode of the two global-state files.
  2. Set both exact paths to 0600.
  3. Allow a normal app state update.
  4. Observe changed birthtime/mtime and mode 0644 on both replacement files.
  5. Repeat a normal state update; the replacements are again 0644.

No content needs to be inspected to reproduce this.

Root cause

The installed global-state writer creates a unique sibling temporary file,
writes UTF-8 data without an explicit file mode, and renames it over the main
or backup destination. Under umask 0022, default creation yields 0644;
rename preserves that mode.

An isolated bundled-Node test produces:

default=0644 explicit_after_rename=0600 umask=0022

Expected behavior

The temporary, main and backup global-state files should be owner-only 0600
on POSIX systems and should remain so across every atomic rewrite.

Suggested fix

Pass an explicit creation mode in both writer paths:

writeFileSync(tempPath, data, { encoding: "utf8", mode: 0o600 });
await writeFile(tempPath, data, { encoding: "utf8", mode: 0o600 });

The temporary filename is unique for each write, so the mode is applied at
creation and is preserved by the atomic rename.

Regression test

Under umask 0022, exercise synchronous and asynchronous saves for the main
and backup files. Assert 0600 after repeated atomic replacements, unchanged
serialized data/JSON parsing, and no Windows regression.

Privacy note

This report intentionally omits the file contents, account identifiers, paths
containing a username, tokens and other private application state.

View original on GitHub ↗