openai/codex · critical

failed to clear CLOEXEC for preserved bubblewrap file descri

Error message

failed to clear CLOEXEC for preserved bubblewrap file descriptor {fd}: {err}

What it means

After reading flags, clear_cloexec writes them back without FD_CLOEXEC via fcntl(fd, F_SETFD) and panics on a negative return. The preceding flag read succeeded, so failure usually means the descriptor was closed between the two fcntl calls (a race), or a seccomp/container filter or exotic kernel denies F_SETFD (EBADF, EINVAL, or EPERM).

Source

Thrown at codex-rs/linux-sandbox/src/exec_util.rs:38

}

fn clear_cloexec(fd: libc::c_int) {
    // SAFETY: `fd` is an owned descriptor kept alive by `files`.
    let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) };
    if flags < 0 {
        let err = std::io::Error::last_os_error();
        panic!("failed to read fd flags for preserved bubblewrap file descriptor {fd}: {err}");
    }
    let cleared_flags = flags & !libc::FD_CLOEXEC;
    if cleared_flags == flags {
        return;
    }

    // SAFETY: `fd` is valid and we are only clearing FD_CLOEXEC.
    let result = unsafe { libc::fcntl(fd, libc::F_SETFD, cleared_flags) };
    if result < 0 {
        let err = std::io::Error::last_os_error();
        panic!("failed to clear CLOEXEC for preserved bubblewrap file descriptor {fd}: {err}");
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use pretty_assertions::assert_eq;
    use tempfile::NamedTempFile;

    #[test]
    fn preserved_files_are_made_inheritable() {
        let file = NamedTempFile::new().expect("temp file");
        set_cloexec(file.as_file().as_raw_fd());

        make_files_inheritable(std::slice::from_ref(file.as_file()));

        assert_eq!(fd_flags(file.as_file().as_raw_fd()) & libc::FD_CLOEXEC, 0);
    }

View on GitHub (pinned to 339751715c)

Solutions

  1. Remove concurrent close/drop of preserved files during launch: hand exclusive ownership to the launch path.
  2. Inspect seccomp profiles and LSM audit logs for denied fcntl(F_SETFD) and allow it for the launcher.
  3. If it persists with a clean ownership model, capture the errno and report upstream with the sandbox launch sequence.
Defensive patterns

Strategy: validation

Validate before calling

use std::os::fd::AsRawFd;
fn cloexec_cleared(files: &[std::fs::File]) -> bool {
    files.iter().all(|f| {
        let fd = f.as_raw_fd();
        // SAFETY: query then clear FD_CLOEXEC on fds owned exclusively by this thread
        let flags = unsafe { libc::fcntl(fd, libc::F_GETFD) };
        flags >= 0 && unsafe { libc::fcntl(fd, libc::F_SETFD, flags & !libc::FD_CLOEXEC) } >= 0
    })
}

Prevention

When it happens

Trigger: Another thread drops or closes a preserved File between the F_GETFD and F_SETFD calls during sandbox launch; a seccomp profile or LSM filtering fcntl on the process; kernel resource exhaustion (ENOMEM).

Common situations: Concurrent shutdown paths closing shared descriptors while a sandbox launch is in flight; hardened containers with aggressive seccomp policies; otherwise extremely rare.

Related errors


AI-assisted analysis of openai/codex@339751715c (2026-08-25). Data as JSON: /api/errors/f2fb2d858e7b7b0e. Report an issue: GitHub.