mirror of
https://github.com/openai/codex.git
synced 2026-10-18 17:18:52 +00:00
## Why Passing extra descriptors to a PTY previously selected behavior intended for inherited escalation sockets: no EOF on stdin close, different signal exit statuses, and no default `SHELL`. Launch attachments need ordinary PTY behavior and must survive execution even when marked `FD_CLOEXEC`. ## What changed - Add `ChildFds::Attached` alongside `ChildFds::Inherited` to distinguish launch attachments from legacy inherited descriptors. - Preserve ordinary EOF, default `SHELL`, and signal exit status behavior for PTY attachments. - Allow explicitly supplied close-on-exec descriptors through pipe and PTY launches without changing the parent's descriptor flags, using child-side flag updates and macOS spawn inheritance actions. - Update existing callers to use `ChildFds::Inherited`. ## Testing Add Unix coverage for attachment accessibility, unchanged parent descriptor flags, default `SHELL`, and signal exit statuses. Extend the PTY stdin-draining test to cover attachments and EOF delivery. GitOrigin-RevId: 864aedc6c5717d8b43c9d71057c9f54409834509
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