openai / openai/codex

ProcessHandle::request_terminate loses retry capability when kill fails

Open
#43,785 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

app-server bug tool-calls
Dominant language
Rust
Stars
125k
Forks
19.4k
PR merge metrics
PR metrics pending

Description

What issue are you seeing?

codex-rs/utils/pty/src/process.rs consumes the owned ChildTerminator before it knows whether hard termination succeeded:

pub fn request_terminate(&self) {
    if let Ok(mut killer_opt) = self.killer.lock()
        && let Some(mut killer) = killer_opt.take()
    {
        let _ = killer.kill();
    }
}

If ChildTerminator::kill() returns an error, the terminator has already been removed from ProcessHandle and the error is discarded. A later termination attempt therefore has no retry capability even though the child may still be alive.

This behavior exists in Codex 0.153.4 and is still present on main as of 2026-09-08. request_terminate() is used by process-control paths including app-server command/process execution, so a transient hard-kill failure can silently turn into a permanently non-retryable handle.

What steps can reproduce the bug?

A deterministic unit regression can use a fake ChildTerminator whose first kill() call returns Err(...) and whose second call succeeds:

  1. Construct a ProcessHandle with that terminator.
  2. Call request_terminate() once; the fake returns an error.
  3. Call request_terminate() again.
  4. Observe that the fake terminator is not invoked a second time because the first call consumed it with take().

The issue is visible directly from the ownership sequence above; no OS-specific race is required to trigger the retry-loss semantics.

What is the expected behavior?

A failed hard-kill attempt should preserve safe retry capability and the failure should be observable. The terminator should only be consumed after a successful terminal action, or request_terminate() should return an error while retaining the terminator when kill() fails.

A regression test should prove first-fail/second-success behavior without permitting PID/process-identity reuse to target an unrelated process.

Additional information

ProcessHandle::signal() already keeps the terminator in place on Unix when signaling fails, which is the retry behavior expected for hard termination as well.

A narrow fix appears possible inside codex-utils-pty; consumers should not need to fork or reconstruct process handles to recover from a failed kill.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Read codex-rs/utils/pty/src/process.rs, especially ProcessHandle::request_terminate() and the existing ProcessHandle::signal() behavior. Add a deterministic regression test in the codex-utils-pty tests using a terminator that fails once and succeeds on retry, then run the package tests. Done means a failed kill remains retryable and the second termination attempt succeeds without PID reuse concerns.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
operating-systems
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.