Skip to content

fix(codex): clean up subprocess on cancellation#3915

Draft
shiwenbin1617 wants to merge 7 commits into
openai:mainfrom
shiwenbin1617:fix/codex-exec-cancellation-cleanup
Draft

fix(codex): clean up subprocess on cancellation#3915
shiwenbin1617 wants to merge 7 commits into
openai:mainfrom
shiwenbin1617:fix/codex-exec-cancellation-cleanup

Conversation

@shiwenbin1617

Copy link
Copy Markdown

Summary

This pull request fixes a subprocess cleanup deadlock in the experimental Codex executor.

When the async output stream was cancelled or closed early, cleanup waited for the stderr drain task before terminating the subprocess. Since stderr remained open while the process was running, cancellation could hang indefinitely and leave the child process unreaped.

The cleanup path now:

  • covers stdin writes, stdout reads, cancellation, and exceptional exits with a common try/finally;
  • terminates and waits for a live subprocess before stopping the stderr drain task;
  • cancels and awaits the optional signal watcher; and
  • preserves the original asyncio.CancelledError semantics.

A deterministic regression test verifies that cancellation completes promptly, the subprocess is killed and reaped, and the stderr task is cancelled.

Test plan

  • bash .agents/skills/code-change-verification/scripts/run.sh
  • pytest -q tests/extensions/experiemental/codex/test_codex_exec_thread.py -k codex_exec_run
  • A local subprocess reproduction confirmed cancellation completes and the child exits with -9.

Issue number

N/A

Checks

  • I've added new tests, if relevant
  • I've run .agents/skills/code-change-verification/scripts/run.sh
  • I've confirmed all verification steps pass
  • If using Codex, I've run /review before submitting this PR

@seratch seratch left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the fix. The original cancellation deadlock is real, but the current cleanup can still hang when stdout has unread buffered data: after process.kill(), await process.wait() blocks if the stdout transport has paused due to backpressure, even though the child has already exited.

Please drain the remaining stdout in bounded chunks before awaiting process.wait(), and add a regression test using a real subprocess that fills stdout beyond the stream-reader limit before the stream is closed. The existing fake-process test cannot reproduce this pipe-level deadlock. I confirmed that draining stdout first resolves the issue while preserving the existing cancellation and stderr-cleanup behavior.

@shiwenbin1617
shiwenbin1617 force-pushed the fix/codex-exec-cancellation-cleanup branch from 53c8fb6 to 8e7af59 Compare July 23, 2026 05:46
@shiwenbin1617

Copy link
Copy Markdown
Author

Thanks for catching this — you were right that waiting for the process before draining a paused stdout transport can still deadlock.

I've updated the cleanup path to cancel any in-flight timeout reader, kill the subprocess, drain the remaining stdout in 64 KiB chunks, and only then await process.wait(). I also added a real-subprocess regression that writes beyond the StreamReader limit, confirms the transport is paused, closes the async generator, and verifies that cleanup completes and the child is reaped.

The existing cancellation and stderr-cleanup coverage remains in place, and the full verification stack passes.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8e7af5931a

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/extensions/experimental/codex/exec.py Outdated
@shiwenbin1617

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Security review completed. No security issues were found in this pull request.

Reviewed commit: da45e04d40

View security finding report

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Swish!

Reviewed commit: da45e04d40

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@shiwenbin1617

Copy link
Copy Markdown
Author

@seratch I addressed the requested stdout backpressure case and the follow-up bot finding in da45e04. Cleanup is now bounded even when an outliving descendant keeps stdout open, with a real-subprocess regression covering that case. Codex review and security review on the latest head are both clean. When you have a chance, could you please take another look? Thanks!

@seratch

seratch commented Jul 23, 2026

Copy link
Copy Markdown
Member

Thanks for addressing the stdout backpressure deadlock. A deeper lifecycle review found two remaining merge blockers.
First, when a descendant holds an inherited pipe, cleanup returns after closing the transport while the descendant remains alive and can continue executing after cancellation. Please ensure cancellation owns and terminates the entire Codex subprocess tree, and add a real-subprocess regression test asserting that the descendant is no longer running.
Second, failures from stdout draining or process.wait() currently replace the original CancelledError or execution exception, and a second cancellation can skip stderr-task cleanup. Please preserve the original exception and make resource cleanup resilient to repeated cancellation, with focused regression coverage for both cases.

@shiwenbin1617

Copy link
Copy Markdown
Author

Addressed both remaining lifecycle blockers in c004807:

  • Codex subprocesses now own a process tree and terminate descendants during cancellation/stream closure on POSIX and Windows. The real-subprocess regression now asserts the inherited-pipe descendant has exited.
  • Cleanup now runs as a shielded task, preserves the original cancellation or execution exception when drain/wait fails, and completes stderr cleanup across repeated cancellation.
  • The Windows coverage avoids the SelectorEventLoop asyncio-subprocess limitation while still exercising real process-tree termination.

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Security review completed. No security issues were found in this pull request.

Reviewed commit: da45e04d40

View security finding report

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: da45e04d40

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/extensions/experimental/codex/exec.py Outdated
Comment thread src/agents/extensions/experimental/codex/exec.py Outdated
@shiwenbin1617

Copy link
Copy Markdown
Author

@codex review

Please review commit c004807 specifically: shiwenbin1617@c004807

The fork branch already points to this commit, while the PR head synchronization is delayed by the ongoing GitHub Pull Requests/Webhooks incident.

@chatgpt-codex-connector

Copy link
Copy Markdown

Security review completed. No security issues were found in this pull request.

Reviewed commit: da45e04d40

View security finding report

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. You're on a roll.

Reviewed commit: da45e04d40

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@shiwenbin1617

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Security review completed. No security issues were found in this pull request.

Reviewed commit: c0048076d5

View security finding report

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c0048076d5

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread tests/extensions/experiemental/codex/test_codex_exec_thread.py Outdated
@shiwenbin1617

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Security review completed. No security issues were found in this pull request.

Reviewed commit: 378c14f764

View security finding report

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 378c14f764

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/agents/extensions/experimental/codex/exec.py Outdated
@shiwenbin1617

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Security review completed. No security issues were found in this pull request.

Reviewed commit: b6bd302799

View security finding report

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Delightful!

Reviewed commit: b6bd302799

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

@shiwenbin1617

Copy link
Copy Markdown
Author

@seratch Thank you for the careful review and for pointing out the deeper lifecycle cases. Thank you as well to Codex for the follow-up checks. I’m sorry for the extra review notifications earlier—the GitHub Pull Requests/Webhooks incident caused two reviews to run against a stale head.

I believe the remaining blockers are now addressed on b6bd302: cleanup owns and terminates descendants across cancellation, errors, repeated cancellation, and clean parent exit, with focused real-subprocess coverage. Codex code and security reviews on the current head are clean.

When you have time, could you please take another look? I appreciate your patience and detailed guidance.

@shiwenbin1617
shiwenbin1617 requested a review from seratch July 23, 2026 12:05
@seratch
seratch marked this pull request as draft July 24, 2026 01:04
@shiwenbin1617

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Security Review

Here are some automated security review suggestions for this pull request.

Reviewed commit: 7552c7204c

View security finding report

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

def _terminate_windows_process_tree(pid: int) -> None:
with contextlib.suppress(OSError, subprocess.TimeoutExpired):
subprocess.run(
["taskkill", "/PID", str(pid), "/T", "/F"],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Security: Pin the Windows taskkill helper path

On Windows this cleanup helper runs taskkill by bare name. If a Codex-enabled app runs from a workspace the model can write to, an attacker can place taskkill.exe there and then trigger cleanup/cancellation, causing the SDK process to execute that binary outside the Codex sandbox. Use an absolute trusted System32 path or a Win32 job/process-tree API instead of PATH/current-directory lookup.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7552c7204c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +244 to +245
# Wait for the direct process independently of pipes inherited by descendants.
await _wait_for_direct_process()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Stop waiting for stdout before reaping descendants

When the direct Codex process exits after emitting its last event but a descendant keeps stdout inherited/open, the next stdout.readline() never returns EOF, so execution never reaches this direct-exit wait or the finally cleanup that kills the process group. I reproduced this with a subprocess that prints one line, spawns a sleeping child with stdout=sys.stdout, and exits; the parent returncode was already 0 while the second anext() hung. Race the direct-exit event with the stdout read, or trigger cleanup once the direct process exits, so clean exits with inherited stdout do not hang indefinitely.

Useful? React with 👍 / 👎.

@shiwenbin1617

Copy link
Copy Markdown
Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Security Review

Here are some automated security review suggestions for this pull request.

Reviewed commit: 8b1c969db2

View security finding report

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

Comment on lines +436 to +437
with contextlib.suppress(ProcessLookupError):
os.killpg(pid, signal.SIGKILL)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Security: Contain daemonized Codex descendants during cleanup

The new cleanup only kills the subprocess' original POSIX process group. A Codex-run command can fork and call setsid()/setpgrp() before cancellation; that descendant leaves pgid pid, so stream.aclose() or an idle timeout returns while it keeps running with the configured workspace/environment. If cancellation is meant to revoke Codex execution, use a containment primitive that covers reparented descendants, or document this as best-effort and enforce hard cleanup in the sandbox/CLI.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — this is a valid limitation. The SDK-level cleanup here is best-effort rather than a security boundary: on POSIX, a descendant that deliberately creates a new session can escape the owned process group, and covering that portably would require an OS-specific containment primitive owned by the Codex CLI or sandbox rather than recursive process scanning in the SDK. This PR will remain scoped to terminating the Codex CLI and ordinary, non-detached descendants during cancellation or stream closure. I’ll leave the implementation unchanged and defer hard containment to the CLI/sandbox layer.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Can't wait for the next one!

Reviewed commit: 8b1c969db2

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants