mirror of
https://github.com/openai/codex.git
synced 2026-10-28 18:58:46 +00:00
## Why Slow consumers can lose process output or block control replies when the in-process event queue fills. Descendants can also keep output streams open after the child exits, leaving clients unable to tell whether captured output is complete. ## What changed - Require delivery of `process/outputDelta` and `process/exited` notifications, while allowing process request replies to proceed when event delivery is blocked. - Exclude time spent delivering output to slow clients from the post-exit drain budget, and drain queued bytes after the deadline closes a stream. - Keep draining after an output byte cap is reached so the child can finish writing; abort each stream's reader when its drain deadline expires. - Add optional `outputDrainTimedOut` to `process/exited` and generated bindings. Report incomplete output separately from the child's exit code, treating a fully captured requested byte cap as complete. - Deliver the exit notification before moving Windows console cleanup to a blocking task. ## Testing Add regression coverage for full event queues, stalled consumers issuing process control requests, slow output delivery, inherited pipes, output caps, Windows console descendants, and compatibility with exit notifications that omit `outputDrainTimedOut`. GitOrigin-RevId: 04cfeea5daa7c2b833cdec3f1fff7efeb4d1a86e
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