openai/codex · error

waitpid failed for bubblewrap child

Error message

waitpid failed for bubblewrap child: {err}

What it means

wait_for_bwrap_child (linux_run_main.rs:930) reaps the forked bwrap child in a loop that retries only EINTR; any other errno panics. For a direct child pid the realistic failure is ECHILD: the child was already reaped elsewhere — a wait-all SIGCHLD handler in the host, another thread calling waitpid(-1), SA_NOCLDWAIT auto-reaping, or a subreaper redirecting the child. The panic aborts the launcher after the sandboxed workload already ran.

Solutions

  1. Remove or scope the global SIGCHLD reaper so each code path reaps only the pids it forked.
  2. Ensure SA_NOCLDWAIT is not set on SIGCHLD while the sandbox runs.
  3. Read the errno in the panic: ECHILD means double reap; EINVAL or anything else means capture a backtrace and report upstream.
  4. Prefer exec'ing the launcher as its own process instead of embedding it in a child-managing supervisor.

Example fix

// before: global reaper steals the sandbox child's status
unsafe { libc::signal(libc::SIGCHLD, reap_all as libc::sighandler_t); }

// after: no global reaper; only the forking code waits its own pids
// (delete reap_all entirely, or make it match a specific pid)
Defensive patterns

Strategy: validation

Validate before calling

fn sigchld_not_auto_reaping() -> bool {
    let mut current: libc::sigaction = unsafe { std::mem::zeroed() };
    unsafe { libc::sigaction(libc::SIGCHLD, std::ptr::null(), &mut current) };
    current.sa_flags & libc::SA_NOCLDWAIT == 0
}

Prevention

When it happens

Trigger: Hosting or wrapping the sandbox launcher in a process that globally reaps children while run_bwrap_in_child_with_synthetic_mount_cleanup or run_bwrap_in_child_capture_stderr calls waitpid on its own forked pid.

Common situations: Supervisors with wait(-1) loops; runtimes or monitoring agents installing SIGCHLD handlers; SA_NOCLDWAIT set for zombie suppression; test harnesses that fork and reap globally.

Related errors


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

Appendix: source

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

                let err = std::io::Error::last_os_error();
                panic!("failed to reset bubblewrap signal handler for {signal}: {err}");
            }
        }
    }
}

fn wait_for_bwrap_child(pid: libc::pid_t) -> libc::c_int {
    loop {
        let mut status: libc::c_int = 0;
        let wait_res = unsafe { libc::waitpid(pid, &mut status as *mut libc::c_int, 0) };
        if wait_res >= 0 {
            return status;
        }
        let err = std::io::Error::last_os_error();
        if err.raw_os_error() == Some(libc::EINTR) {
            continue;
        }
        panic!("waitpid failed for bubblewrap child: {err}");
    }
}

fn register_synthetic_mount_targets(
    targets: &[crate::bwrap::SyntheticMountTarget],
) -> Vec<SyntheticMountTargetRegistration> {
    with_synthetic_mount_registry_lock(|| {
        targets
            .iter()
            .map(|target| {
                let marker_dir = synthetic_mount_marker_dir(target.path());
                fs::create_dir_all(&marker_dir).unwrap_or_else(|err| {
                    panic!(
                        "failed to create synthetic bubblewrap mount marker directory {}: {err}",
                        marker_dir.display()
                    )
                });
                let target = if target.preserves_pre_existing_path()

View on GitHub (pinned to 339751715c)