mirror of
https://github.com/openai/codex.git
synced 2026-04-27 16:15:09 +00:00
fix: preserve zsh-fork escalation fds across unified-exec spawn paths (#13644)
## Why `zsh-fork` sessions launched through unified-exec need the escalation socket to survive the wrapper -> server -> child handoff so later intercepted `exec()` calls can still reach the escalation server. The inherited-fd spawn path also needs to avoid closing Rust's internal exec-error pipe, and the shell-escalation handoff needs to tolerate the receive-side case where a transferred fd is installed into the same stdio slot it will be mapped onto. ## What Changed - Added `SpawnLifecycle::inherited_fds()` in `codex-rs/core/src/unified_exec/process.rs` and threaded inherited fds through `codex-rs/core/src/unified_exec/process_manager.rs` so unified-exec can preserve required descriptors across both PTY and no-stdin pipe spawn paths. - Updated `codex-rs/core/src/tools/runtimes/shell/zsh_fork_backend.rs` to expose the escalation socket fd through the spawn lifecycle. - Added inherited-fd-aware spawn helpers in `codex-rs/utils/pty/src/pty.rs` and `codex-rs/utils/pty/src/pipe.rs`, including Unix pre-exec fd pruning that preserves requested inherited fds while leaving `FD_CLOEXEC` descriptors alone. The pruning helper is now named `close_inherited_fds_except()` to better describe that behavior. - Updated `codex-rs/shell-escalation/src/unix/escalate_client.rs` to duplicate local stdio before transfer and send destination stdio numbers in `SuperExecMessage`, so the wrapper keeps using its own `stdin`/`stdout`/`stderr` until the escalated child takes over. - Updated `codex-rs/shell-escalation/src/unix/escalate_server.rs` so the server accepts the overlap case where a received fd reuses the same stdio descriptor number that the child setup will target with `dup2`. - Added comments around the PTY stdio wiring and the overlap regression helper to make the fd handoff and controlling-terminal setup easier to follow. ## Verification - `cargo test -p codex-utils-pty` - covers preserved-fd PTY spawn behavior, PTY resize, Python REPL continuity, exec-failure reporting, and the no-stdin pipe path - `cargo test -p codex-shell-escalation` - covers duplicated-fd transfer on the client side and verifies the overlap case by passing a pipe-backed stdin payload through the server-side `dup2` path --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/13644). * #14624 * __->__ #13644
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
use core::fmt;
|
||||
use std::io;
|
||||
#[cfg(unix)]
|
||||
use std::os::fd::RawFd;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex as StdMutex;
|
||||
@@ -41,9 +43,24 @@ impl From<TerminalSize> for PtySize {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
pub(crate) trait PtyHandleKeepAlive: Send {}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl<T: Send + ?Sized> PtyHandleKeepAlive for T {}
|
||||
|
||||
pub(crate) enum PtyMasterHandle {
|
||||
Resizable(Box<dyn MasterPty + Send>),
|
||||
#[cfg(unix)]
|
||||
Opaque {
|
||||
raw_fd: RawFd,
|
||||
_handle: Box<dyn PtyHandleKeepAlive>,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct PtyHandles {
|
||||
pub _slave: Option<Box<dyn SlavePty + Send>>,
|
||||
pub _master: Box<dyn MasterPty + Send>,
|
||||
pub(crate) _master: PtyMasterHandle,
|
||||
}
|
||||
|
||||
impl fmt::Debug for PtyHandles {
|
||||
@@ -131,7 +148,11 @@ impl ProcessHandle {
|
||||
let handles = handles
|
||||
.as_ref()
|
||||
.ok_or_else(|| anyhow!("process is not attached to a PTY"))?;
|
||||
handles._master.resize(size.into())
|
||||
match &handles._master {
|
||||
PtyMasterHandle::Resizable(master) => master.resize(size.into()),
|
||||
#[cfg(unix)]
|
||||
PtyMasterHandle::Opaque { raw_fd, .. } => resize_raw_pty(*raw_fd, size),
|
||||
}
|
||||
}
|
||||
|
||||
/// Close the child's stdin channel.
|
||||
@@ -184,6 +205,21 @@ impl Drop for ProcessHandle {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn resize_raw_pty(raw_fd: RawFd, size: TerminalSize) -> anyhow::Result<()> {
|
||||
let mut winsize = libc::winsize {
|
||||
ws_row: size.rows,
|
||||
ws_col: size.cols,
|
||||
ws_xpixel: 0,
|
||||
ws_ypixel: 0,
|
||||
};
|
||||
let result = unsafe { libc::ioctl(raw_fd, libc::TIOCSWINSZ, &mut winsize) };
|
||||
if result == -1 {
|
||||
return Err(std::io::Error::last_os_error().into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Combine split stdout/stderr receivers into a single broadcast receiver.
|
||||
pub fn combine_output_receivers(
|
||||
mut stdout_rx: mpsc::Receiver<Vec<u8>>,
|
||||
|
||||
Reference in New Issue
Block a user