openai/codex · error

failed to create bubblewrap exec start pipe: {err}

Error message

failed to create bubblewrap exec start pipe: {err}

What it means

When protected-create targets are present, the parent creates an O_CLOEXEC pipe (create_exec_start_pipe, linux_run_main.rs:753) that gates the child: bwrap only execs after wait_for_parent_exec_start sees the parent's byte, written once signal forwarders and the protected-create monitor are armed. pipe2 failing is almost always EMFILE (RLIMIT_NOFILE reached) or ENFILE (system-wide fd table full); the panic aborts the run before fork.

Source

Thrown at codex-rs/linux-sandbox/src/linux_run_main.rs:760

    }
}

impl Drop for ProtectedCreateWatcher {
    fn drop(&mut self) {
        unsafe {
            libc::close(self.fd);
        }
    }
}

fn create_exec_start_pipe(enabled: bool) -> [libc::c_int; 2] {
    if !enabled {
        return [-1, -1];
    }
    let mut pipe = [-1, -1];
    if unsafe { libc::pipe2(pipe.as_mut_ptr(), libc::O_CLOEXEC) } < 0 {
        let err = std::io::Error::last_os_error();
        panic!("failed to create bubblewrap exec start pipe: {err}");
    }
    pipe
}

fn wait_for_parent_exec_start(read_fd: libc::c_int, write_fd: libc::c_int) {
    if write_fd >= 0 {
        unsafe {
            libc::close(write_fd);
        }
    }
    if read_fd < 0 {
        return;
    }

    let mut byte = [0_u8; 1];
    loop {
        let read = unsafe { libc::read(read_fd, byte.as_mut_ptr().cast(), byte.len()) };
        if read >= 0 {

View on GitHub (pinned to 339751715c)

Solutions

  1. Raise the fd limit before launching: ulimit -n 65536 (or LimitNOFILE=65536 in the service unit).
  2. Inspect /proc/<pid>/fd to find and fix descriptor leaks.
  3. Reduce the number of concurrent sandboxed sessions in one process.
  4. If the errno is ENFILE, find the system-wide fd hog (lsof) and restart it.

Example fix

// before: launching with the inherited low limit
run_sandboxed(cmd);

// after: raise RLIMIT_NOFILE to the hard limit first
unsafe {
    let mut lim: libc::rlimit = std::mem::zeroed();
    libc::getrlimit(libc::RLIMIT_NOFILE, &mut lim);
    lim.rlim_cur = lim.rlim_max;
    libc::setrlimit(libc::RLIMIT_NOFILE, &lim);
}
run_sandboxed(cmd);
Defensive patterns

Strategy: validation

Validate before calling

fn fd_headroom(min_free: usize) -> bool {
    let used = std::fs::read_dir("/proc/self/fd").map(|d| d.count()).unwrap_or(usize::MAX);
    let mut lim: libc::rlimit = unsafe { std::mem::zeroed() };
    unsafe { libc::getrlimit(libc::RLIMIT_NOFILE, &mut lim) };
    (lim.rlim_cur as usize).saturating_sub(used) >= min_free
}

Prevention

When it happens

Trigger: Running a sandboxed command that includes protected_create_targets (the pipe is created only when they are non-empty, linux_run_main.rs:579) from a process at its fd ceiling: leaked descriptors, many concurrent sessions, or a low soft limit (ulimit -n 256/1024).

Common situations: CI shells and systemd services with default LimitNOFILE=1024; long-lived processes leaking sockets or files; heavy parallel sandboxed sessions each holding pipes.

Related errors


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