openai/codex · error
waitpid failed for bubblewrap child: {err}
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.
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)
Solutions
- Remove or scope the global SIGCHLD reaper so each code path reaps only the pids it forked.
- Ensure SA_NOCLDWAIT is not set on SIGCHLD while the sandbox runs.
- Read the errno in the panic: ECHILD means double reap; EINVAL or anything else means capture a backtrace and report upstream.
- 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
- Never install wait-all SIGCHLD handlers in a process hosting the sandbox launcher.
- Always wait on the exact pid you forked; do not reap foreign children.
- Run the launcher as a standalone process when a supervisor must manage other children.
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
- failed to open bundled bubblewrap {}: {err}
- invalid bundled bubblewrap fd path: {err}
- failed to exec bundled bubblewrap {} via {fd_path}: {err}
- failed to normalize bundled bubblewrap path {}: {err}
- failed to read fd flags for preserved bubblewrap file descri
AI-assisted analysis of openai/codex@339751715c (2026-08-25).
Data as JSON: /api/errors/b3762467422d7fd9.
Report an issue: GitHub.