{"record":{"id":"b5e65b2d78da4617","repo":"openai/codex","slug":"failed-to-create-bubblewrap-exec-start-pipe-err","errorCode":null,"errorMessage":"failed to create bubblewrap exec start pipe: {err}","messagePattern":"failed to create bubblewrap exec start pipe: (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"codex-rs/linux-sandbox/src/linux_run_main.rs","lineNumber":760,"sourceCode":"    }\n}\n\nimpl Drop for ProtectedCreateWatcher {\n    fn drop(&mut self) {\n        unsafe {\n            libc::close(self.fd);\n        }\n    }\n}\n\nfn create_exec_start_pipe(enabled: bool) -> [libc::c_int; 2] {\n    if !enabled {\n        return [-1, -1];\n    }\n    let mut pipe = [-1, -1];\n    if unsafe { libc::pipe2(pipe.as_mut_ptr(), libc::O_CLOEXEC) } < 0 {\n        let err = std::io::Error::last_os_error();\n        panic!(\"failed to create bubblewrap exec start pipe: {err}\");\n    }\n    pipe\n}\n\nfn wait_for_parent_exec_start(read_fd: libc::c_int, write_fd: libc::c_int) {\n    if write_fd >= 0 {\n        unsafe {\n            libc::close(write_fd);\n        }\n    }\n    if read_fd < 0 {\n        return;\n    }\n\n    let mut byte = [0_u8; 1];\n    loop {\n        let read = unsafe { libc::read(read_fd, byte.as_mut_ptr().cast(), byte.len()) };\n        if read >= 0 {","sourceCodeStart":742,"sourceCodeEnd":778,"githubUrl":"https://github.com/openai/codex/blob/339751715c64496cb86246bfb3935f40e309dd3d/codex-rs/linux-sandbox/src/linux_run_main.rs#L742-L778","documentation":"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.","triggerScenarios":"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).","commonSituations":"CI shells and systemd services with default LimitNOFILE=1024; long-lived processes leaking sockets or files; heavy parallel sandboxed sessions each holding pipes.","solutions":["Raise the fd limit before launching: ulimit -n 65536 (or LimitNOFILE=65536 in the service unit).","Inspect /proc/<pid>/fd to find and fix descriptor leaks.","Reduce the number of concurrent sandboxed sessions in one process.","If the errno is ENFILE, find the system-wide fd hog (lsof) and restart it."],"exampleFix":"// before: launching with the inherited low limit\nrun_sandboxed(cmd);\n\n// after: raise RLIMIT_NOFILE to the hard limit first\nunsafe {\n    let mut lim: libc::rlimit = std::mem::zeroed();\n    libc::getrlimit(libc::RLIMIT_NOFILE, &mut lim);\n    lim.rlim_cur = lim.rlim_max;\n    libc::setrlimit(libc::RLIMIT_NOFILE, &lim);\n}\nrun_sandboxed(cmd);","handlingStrategy":"validation","validationCode":"fn fd_headroom(min_free: usize) -> bool {\n    let used = std::fs::read_dir(\"/proc/self/fd\").map(|d| d.count()).unwrap_or(usize::MAX);\n    let mut lim: libc::rlimit = unsafe { std::mem::zeroed() };\n    unsafe { libc::getrlimit(libc::RLIMIT_NOFILE, &mut lim) };\n    (lim.rlim_cur as usize).saturating_sub(used) >= min_free\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Set a generous RLIMIT_NOFILE in service definitions and CI shells.","Track /proc/<pid>/fd growth to catch leaks before EMFILE.","Bound the number of concurrent sandboxed sessions per process."],"tags":["sandbox","bubblewrap","pipe","file-descriptor","emfile","resource-exhaustion"],"backgroundTag":"too-many-open-files","analyzedSha":"339751715c64496cb86246bfb3935f40e309dd3d","analyzedAt":"2026-08-25T05:35:09.876Z","schemaVersion":2},"datasetVersion":"2026-08-25T06:17:31.827Z"}