mirror of
https://github.com/openai/codex.git
synced 2026-09-03 14:59:03 +00:00
## Why Signaling an MCP server's process group can fail with `EPERM` on macOS, preventing owned descendants from being terminated. ## What changed - When a group signal is denied, enumerate the group's processes and signal members that still belong to the expected process group. - Apply the fallback to both `SIGTERM` and `SIGKILL` during MCP stdio server cleanup, while retaining the existing group signaling behavior elsewhere. - Reject invalid process group IDs and avoid signaling processes that have moved to another group. ## Testing Added macOS tests covering cleanup with a live or exited group leader, `SIGTERM`-resistant descendants, escalation to `SIGKILL`, and unsafe group IDs. GitOrigin-RevId: db722475b94dece74244ce8109b3e92f6cfb202a
codex-utils-pty
Lightweight helpers for spawning interactive processes either under a PTY (pseudo terminal) or regular pipes. The public API is minimal and mirrors both backends so callers can switch based on their needs (e.g., enabling or disabling TTY).
API surface
spawn_pty_process(program, args, cwd, env, arg0, size)→SpawnedProcessspawn_pipe_process(program, args, cwd, env, arg0)→SpawnedProcessspawn_pipe_process_no_stdin(program, args, cwd, env, arg0)→SpawnedProcesscombine_output_receivers(stdout_rx, stderr_rx)→broadcast::Receiver<Vec<u8>>conpty_supported()→bool(Windows only; always true elsewhere)TerminalSize { rows, cols }selects PTY dimensions in character cells.ProcessHandleexposes:writer_sender()→mpsc::Sender<Vec<u8>>(stdin)resize(TerminalSize)close_stdin()has_exited(),exit_code(),terminate()
SpawnedProcessbundlessession,stdout_rx,stderr_rx, andexit_rx(oneshot exit code).
Usage examples
use std::collections::HashMap;
use std::path::Path;
use codex_utils_pty::combine_output_receivers;
use codex_utils_pty::spawn_pty_process;
use codex_utils_pty::TerminalSize;
# tokio_test::block_on(async {
let env_map: HashMap<String, String> = std::env::vars().collect();
let spawned = spawn_pty_process(
"bash",
&["-lc".into(), "echo hello".into()],
Path::new("."),
&env_map,
&None,
TerminalSize::default(),
).await?;
let writer = spawned.session.writer_sender();
writer.send(b"exit\n".to_vec()).await?;
// Collect output until the process exits.
let mut output_rx = combine_output_receivers(spawned.stdout_rx, spawned.stderr_rx);
let mut collected = Vec::new();
while let Ok(chunk) = output_rx.try_recv() {
collected.extend_from_slice(&chunk);
}
let exit_code = spawned.exit_rx.await.unwrap_or(-1);
# let _ = (collected, exit_code);
# anyhow::Ok(())
# });
Swap in spawn_pipe_process for a non-TTY subprocess; the rest of the API stays the same.
Use spawn_pipe_process_no_stdin to force stdin closed (commands that read stdin will see EOF immediately).
Tests
Unit tests live in src/lib.rs and cover both backends (PTY Python REPL and pipe-based stdin roundtrip). Run with:
just test -p codex-utils-pty --no-capture