Skip to content

fix(wake): reap the detached app-server when MCP cancels or times out a wake #231

Closed

Description

@abruption

What happened

Security and stability hardening audit finding H04 / P2 (2026-09-28). Priority is an audit recommendation, not a claim of a production incident.

The MCP adapter starts the CLI in its own session and, on cancellation or timeout, calls stop_codex_wake(process) with the default 0.2 s grace: SIGTERM to the CLI process group, then SIGKILL. A --wake CLI, however, starts codex app-server with start_new_session=True, so the app-server is outside the group the adapter signals. Only the CLI's own SIGTERM handler reaches it, by raising KeyboardInterrupt and running the same TERM, 0.2 s, KILL sequence against the app-server group.

Both 0.2 s grace periods start at nearly the same moment. If the app-server does not exit within the grace period, the adapter can SIGKILL the CLI before the CLI escalates to SIGKILL on its child. The detached app-server then keeps running the resumed turn, which can continue to spend model usage and edit files after the MCP caller saw the wake as cancelled.

Isolated fixture: a fake app-server that ignores SIGTERM, answers initialize/thread/resume, and never completes the turn. It ran under run_codex_wake in a child started the same way the adapter starts the CLI, and then stop_codex_wake was applied to that child. The CLI returned -9 every time, and the app-server was still alive 1 s later in 3 of 5 runs. The audit's earlier run had 2 of 3. Only fixture processes were signalled and cleaned up.

What you expected

Cancelling or timing out an MCP wake leaves no app-server that session-peer started. One option: the adapter waits for the CLI's worst-case cleanup budget (grace, wait(timeout=3), Darwin ps check) before SIGKILL. Another: the CLI reports the app-server PGID so the parent can reap it with the existing PID-reuse safeguards.

Environment

  • Baseline: published session-peer v1.0.2, commit 2046836822b88e53246de73851186598604cb5ec.
  • Reproduction: isolated source fixtures with a synthetic app-server; no live message/ACK tests or production mutation.
  • Audit environment: macOS arm64, Python 3.14.7.
  • Discovery output: not applicable to this fixture; no real session identifiers or credentials are included.

Source evidence

Acceptance criteria

  • Add MCP cancellation and timeout fixtures with a TERM-resistant app-server started via --wake, and require that no app-server started by session-peer survives the cleanup deadline.
  • Keep cleanup bounded and limited to process groups session-peer owns; never signal a reused or unrelated PGID.
  • Cover prompt app-server exit, SIGTERM-ignoring app-server, repeated cancellation, and normal completion.
  • Keep outcome semantics: a queued message is not reported as retracted, and the wake result stays unknown/activation_interrupted where applicable.

Scope and limitations

The fixture does not measure how long a real codex app-server takes to shut down on SIGTERM or stdin EOF. The race needs an app-server that is still running after the grace period.

Target: v1.0.3. This issue does not authorize a release or operating-service changes.

Related, not duplicate: #205 (descendants in the same app-server group after the leader exits) and #206 (CLI descendants in the MCP-owned group). Neither covers a child placed in a separate session by the CLI.

Checked

  • Searched existing issues for duplicates.
  • Removed credentials, conversation contents, private paths and real device/session identifiers.
  • Used isolated fixtures without weakening inbound permissions.

Activity

  1. added this to the v1.0.3 milestone on Sep 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions