Linux sandbox regression in v0.81: workspace-write fails with EPERM writing /proc/self/uid_map

Resolved 💬 3 comments Opened Jan 14, 2026 by Mygod Closed Jan 14, 2026

What version of Codex is running?

codex-cli 0.81.0

What subscription do you have?

Pro

Which model were you using?

5.2 codex

What platform is your computer?

Linux 6.8.0-90-generic x86_64 x86_64

What issue are you seeing?

After upgrading to v0.81, every command prompts “command failed; retry without sandbox?”.
Running the sandbox directly fails:
thread 'main' panicked at linux-sandbox/src/linux_run_main.rs:30:9:
error running landlock: Io(Os { code: 1, kind: PermissionDenied, message: "Operation not permitted" })

What steps can reproduce the bug?

1) Run the Linux sandbox directly:
codex-linux-sandbox --sandbox-policy-cwd "$PWD" --sandbox-policy '{"type":"workspace-write"}' -- true
2) Observe it fails with EPERM as above.
3) Alternatively, run Codex normally with the default workspace-write sandbox and execute any command; it repeatedly prompts “command failed; retry without sandbox?”.

What is the expected behavior?

Sandbox should initialize successfully and commands should run without asking to retry outside the sandbox.

Additional information

  • This was not happening on v0.80.
  • Root cause on this host: after unshare, codex-linux-sandbox writes uid_map using getuid()/getgid() from inside the user namespace, which become 65534 (overflow UID). Writing “0 65534 1” to /proc/self/uid_map fails with EPERM.
  • Fix that resolves it here: capture original UID/GID before unshare and use those for uid_map/gid_map in codex-rs/linux-sandbox/src/mounts.rs.
  • Sysctls permit user namespaces and Landlock is loaded.

The patch that fixes the issue (verified locally):

diff --git a/codex-rs/linux-sandbox/src/mounts.rs b/codex-rs/linux-sandbox/src/mounts.rs
index f84afad8d..713d50108 100644
--- a/codex-rs/linux-sandbox/src/mounts.rs
+++ b/codex-rs/linux-sandbox/src/mounts.rs
@@ -20,13 +20,16 @@ pub(crate) fn apply_read_only_mounts(sandbox_policy: &SandboxPolicy, cwd: &Path)
          return Ok(());
     }

+    let original_uid = unsafe { libc::getuid() };
+    let original_gid = unsafe { libc::getgid() };
+
     // Root can unshare the mount namespace directly; non-root needs a user
     // namespace to gain capabilities for remounting.
     if is_running_as_root() {
          unshare_mount_namespace()?;
     } else {
          unshare_user_and_mount_namespaces()?;
-        write_user_namespace_maps()?;
+        write_user_namespace_maps(original_uid, original_gid)?;
     }
     make_mounts_private()?;

@@ -153,11 +156,9 @@ struct CapUserData {
const LINUX_CAPABILITY_VERSION_3: u32 = 0x2008_0522;

/// Map the current uid/gid to root inside the user namespace.
-fn write_user_namespace_maps() -> Result<()> {
+fn write_user_namespace_maps(uid: libc::uid_t, gid: libc::gid_t) -> Result<()> {
     write_proc_file("/proc/self/setgroups", "deny\n")?;

-    let uid = unsafe { libc::getuid() };
-    let gid = unsafe { libc::getgid() };
     write_proc_file("/proc/self/uid_map", format!("0 {uid} 1\n"))?;
     write_proc_file("/proc/self/gid_map", format!("0 {gid} 1\n"))?;
     Ok(())

(The above is generated with codex.)

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗