codex exec fails in an outer read-only sandbox because installation_id requires write access
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 125k
- Forks
- 19.4k
- PR merge metrics
- PR metrics pending
Description
What version of Codex CLI is running?
codex-cli 0.151.0
The rust-v0.152.0 source has an identical installation_id.rs and still calls its resolver during in-process app-server startup. 0.152.0 was inspected, not installed or run.
What subscription do you have?
Pro
Which model were you using?
No model turn was reached; the CLI failed during startup.
What platform is your computer?
macOS 27.0, Apple Silicon (arm64).
What terminal emulator and version are you using (if applicable)?
The CLI was launched as a subprocess from a Codex Desktop task.
Codex doctor report
Not collected. Startup logs and a focused filesystem probe are described below.
What issue are you seeing?
A nested codex exec fails before emitting any JSON events when the enclosing task sandbox allows reading ~/.codex/installation_id but denies writing it:
Error: failed to initialize in-process app-server client: Operation not permitted (os error 1)
Two launches exited with code 1 and empty stdout, requesting inner read-only and workspace-write sandboxes respectively. Both inherited the same outer restrictions. Redirecting logs and SQLite to writable fixture directories, disabling analytics, and using --ephemeral did not avoid the failure.
This blocks starting a read-only worker with existing Codex state. The diagnostic does not identify which file requires write access.
What steps can reproduce the bug?
-
On macOS, launch a child CLI from an enclosing Codex task that denies writes to
~/.codex/installation_idwhile allowing reads. The observed file already existed, was user-owned, and had mode0644. -
Provide a synthetic Git repository and writable directories for logs, SQLite, temporary files, and caches. Retain the existing Codex home.
-
Launch
codex execwith these relevant arguments:codex -a never exec --sandbox read-only --ephemeral --json \ -C /path/to/synthetic-repo \ -c 'log_dir="/path/to/writable-logs"' \ -c 'sqlite_home="/path/to/writable-sqlite"' \ -c 'analytics.enabled=false' \ -c 'history.persistence="none"' \ "Read sample.py."This is a shortened representation of the recorded invocation, not an independently tested minimal reproducer. The actual launches also configured a fixture MCP server, disabled unrelated plugins/hooks, and used longer validation prompts. The fixture MCP connection was never reached.
-
Observe the startup error above. Setting the inner CLI's
--sandbox read-onlyalone does not create the required outer restriction; that flag controls the worker's tool execution.
A separate probe in the same enclosing sandbox confirmed:
| Operation on the existing installation-ID file | Result |
|---|---|
os.open(path, os.O_RDONLY) |
Succeeds |
os.open(path, os.O_RDWR) |
Fails with EPERM / errno 1 |
| File contents before and after the probe | Unchanged |
What is the expected behavior?
Reusing a valid existing installation ID should not require opening the file for writing. If startup requires a write, its error should name the path and operation that failed.
Additional information
In 0.151.0's resolver, the file is opened with:
options.read(true).write(true).create(true);
This happens before reading and validating the existing UUID. 0.152.0 retains that implementation, and app-server startup propagates its error.
The denied writable open and this call path identify a startup blocker. A patched binary has not been tested, so this report does not establish that removing this blocker is sufficient for the entire nested session to run.
Consider reading and reusing a valid ID without a writable open, retaining the locked write path for creation or repair. Regression coverage should include an existing valid ID with writes denied, missing/invalid IDs in a writable home, and concurrent initialization.
Related upstream work:
- #16912 introduced the persistent installation ID and writable-open behavior.
- #21182 moved resolution to host startup and let internal child sessions reuse the parent's ID. A separately launched CLI still resolves the file itself.
- #26277 discusses Android file-lock failures in the same startup path; the observed failure here occurs when opening the file for writing on macOS.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with codex-rs/core/src/installation_id.rs, focusing on the resolver's file-open behavior, then trace its propagation through codex-rs/app-server/src/in_process.rs. Add regression coverage for an existing valid ID with writes denied, missing or invalid IDs in a writable home, and concurrent initialization; done means the read-only case reuses the ID without requiring write access.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- cli, operating-systems
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 68/100