What version of Codex is running?
0.44.0
Which model were you using?
gpt-5-codex
What platform is your computer?
Darwin 24.6.0 arm64 arm
What steps can reproduce the bug?
When Codex exec terminates a child process (due to timeout or Ctrl+C), it
calls start_kill() but never calls wait(). This creates zombie processes
that hold file descriptors.
Location: codex-rs/core/src/exec.rs lines 339 and 345
Err(_) => {
// timeout
child.start_kill()?;
// Debatable whether `child.wait().await` should be called here. ⚠️
(synthetic_exit_status(EXIT_CODE_SIGNAL_BASE + TIMEOUT_CODE), true)
}
To reproduce:
- Run Codex with a timeout that will trigger:
timeout 2s codex exec "sleep 10"
- After timeout, check for zombie processes:
ps aux | grep codex | grep defunct
- Try to remove the working directory while Codex was running:
rm -rf /path/to/worktree
-> Fails with "Directory busy" or "Resource busy"
The issue only appears with 0.44.0 and can be traced down to b8e1fe6
What is the expected behavior?
After calling start_kill(), the parent process should call wait() to reap
the terminated child process. This is standard Unix process management:
Err(_) => {
child.start_kill()?;
let _ = child.wait().await; // ✅ Reap the zombie
(synthetic_exit_status(EXIT_CODE_SIGNAL_BASE + TIMEOUT_CODE), true)
}
Expected:
- No zombie processes after timeout/Ctrl+C
- File descriptors (including CWD) are released immediately
- Host applications can clean up worktree directories without errors
What do you see instead?
- Zombie processes persist until the host application exits:
$ ps aux | grep codex
codex 12345 0.0 0.0 0 0 ?? Z <defunct>
- File descriptors remain open, preventing directory cleanup:
$ rm -rf /path/to/worktree
rm: cannot remove '/path/to/worktree': Device or resource busy
- Resource leaks accumulate in long-running host applications that manage
multiple Codex sessions.
Root cause: Without wait(), terminated processes become zombies and retain
their open file descriptors (including the current working directory).
Additional information
This affects any application that:
- Runs Codex in isolated git worktrees (common in CI/CD and development
tools)
- Manages multiple concurrent Codex sessions
- Needs to clean up directories after sessions complete
Why This Became More Noticeable in v0.44
Commit b8e1fe60c enabled process hardening (PT_DENY_ATTACH) by default.
While this is a good security feature, it may slightly extend process
termination time, making the missing wait() call more apparent. However,
the bug exists regardless of process hardening.
The Fix
Add child.wait().await after both start_kill() calls in
codex-rs/core/src/exec.rs:
Line 339 (timeout case):
Err(_) => {
child.start_kill()?;
let _ = tokio::time::timeout(Duration::from_secs(1), child.wait()).await;
(synthetic_exit_status(EXIT_CODE_SIGNAL_BASE + TIMEOUT_CODE), true)
}
Line 345 (Ctrl+C case):
_ = tokio::signal::ctrl_c() => {
child.start_kill()?;
let _ = tokio::time::timeout(Duration::from_secs(1), child.wait()).await;
(synthetic_exit_status(EXIT_CODE_SIGNAL_BASE + SIGKILL_CODE), false)
}
see in the docs: https://docs.rs/tokio/latest/tokio/process/struct.Child.html#method.kill
What version of Codex is running?
0.44.0
Which model were you using?
gpt-5-codex
What platform is your computer?
Darwin 24.6.0 arm64 arm
What steps can reproduce the bug?
When Codex exec terminates a child process (due to timeout or Ctrl+C), it
calls
start_kill()but never callswait(). This creates zombie processesthat hold file descriptors.
Location:
codex-rs/core/src/exec.rslines 339 and 345To reproduce:
timeout 2s codex exec "sleep 10"
ps aux | grep codex | grep defunct
rm -rf /path/to/worktree
-> Fails with "Directory busy" or "Resource busy"
The issue only appears with 0.44.0 and can be traced down to b8e1fe6
What is the expected behavior?
After calling
start_kill(), the parent process should callwait()to reapthe terminated child process. This is standard Unix process management:
Expected:
What do you see instead?
$ rm -rf /path/to/worktree
rm: cannot remove '/path/to/worktree': Device or resource busy
multiple Codex sessions.
Root cause: Without wait(), terminated processes become zombies and retain
their open file descriptors (including the current working directory).
Additional information
This affects any application that:
tools)
Why This Became More Noticeable in v0.44
Commit
b8e1fe60cenabled process hardening (PT_DENY_ATTACH) by default.While this is a good security feature, it may slightly extend process
termination time, making the missing
wait()call more apparent. However,the bug exists regardless of process hardening.
The Fix
Add
child.wait().awaitafter bothstart_kill()calls incodex-rs/core/src/exec.rs:Line 339 (timeout case):
see in the docs: https://docs.rs/tokio/latest/tokio/process/struct.Child.html#method.kill