[codex] Add unsandboxed process exec API (#19040)

## Why

App-server clients sometimes need argv-based local process execution
while sandbox policy is controlled outside Codex. Those environments can
reject sandbox-disabling paths before a command ever starts, even when
the caller intentionally wants unsandboxed execution.

This PR adds a distinct `process/*` API for that use case instead of
extending `command/exec` with another sandbox-disabling shape. Keeping
the new surface separate also makes the future removal of `command/exec`
simpler: clients that need explicit process lifecycle control can move
to the newer handle-based API without depending on `command/exec`
business logic.

## What changed

- Added v2 process lifecycle methods: `process/spawn`,
`process/writeStdin`, `process/resizePty`, and `process/kill`.
- Added process notifications: `process/outputDelta` for streamed
stdout/stderr chunks and `process/exited` for final exit status and
buffered output.
- Made `process/spawn` intentionally unsandboxed and omitted
sandbox-selection fields such as `sandboxPolicy` and
`permissionProfile`.
- Added client-supplied, connection-scoped `processHandle` values for
follow-up control requests and notification routing.
- Supported cwd, environment overrides, PTY mode and size, stdin
streaming, stdout/stderr streaming, per-stream output caps, and timeout
controls.
- Killed active process sessions when the originating app-server
connection closes.
- Wired the implementation through the modular `request_processors/`
app-server layout, with process-handle request serialization for
follow-up control calls.
- Updated generated JSON/TypeScript schema fixtures and documented the
new API in `codex-rs/app-server/README.md`.
- Added v2 app-server integration coverage in
`codex-rs/app-server/tests/suite/v2/process_exec.rs` for spawn
acknowledgement before exit, buffered output caps, and process
termination.

## Verification

- `cargo test -p codex-app-server-protocol`
- `cargo test -p codex-app-server`

---------

Co-authored-by: Owen Lin <owen@openai.com>
This commit is contained in:
Ruslan Nigmatullin
2026-05-04 16:43:58 -07:00
committed by GitHub
Unverified
parent a8db4af5c3
commit 4950e7d8a6
25 changed files with 2223 additions and 2 deletions
@@ -80,6 +80,7 @@ pub enum ClientRequestSerializationScope {
Thread { thread_id: String },
ThreadPath { path: PathBuf },
CommandExecProcess { process_id: String },
Process { process_handle: String },
FuzzyFileSearchSession { session_id: String },
FsWatch { watch_id: String },
McpOauth { server_name: String },
@@ -127,6 +128,11 @@ macro_rules! serialization_scope_expr {
process_id: $actual_params.$field.clone(),
})
};
($actual_params:ident, process_handle($params:ident . $field:ident)) => {
Some(ClientRequestSerializationScope::Process {
process_handle: $actual_params.$field.clone(),
})
};
($actual_params:ident, fuzzy_session_id($params:ident . $field:ident)) => {
Some(ClientRequestSerializationScope::FuzzyFileSearchSession {
session_id: $actual_params.$field.clone(),
@@ -900,6 +906,34 @@ client_request_definitions! {
serialization: command_process_id(params.process_id),
response: v2::CommandExecResizeResponse,
},
#[experimental("process/spawn")]
/// Spawn a standalone process (argv vector) without a Codex sandbox.
ProcessSpawn => "process/spawn" {
params: v2::ProcessSpawnParams,
serialization: process_handle(params.process_handle),
response: v2::ProcessSpawnResponse,
},
#[experimental("process/writeStdin")]
/// Write stdin bytes to a running `process/spawn` session or close stdin.
ProcessWriteStdin => "process/writeStdin" {
params: v2::ProcessWriteStdinParams,
serialization: process_handle(params.process_handle),
response: v2::ProcessWriteStdinResponse,
},
#[experimental("process/kill")]
/// Terminate a running `process/spawn` session by client-supplied `processHandle`.
ProcessKill => "process/kill" {
params: v2::ProcessKillParams,
serialization: process_handle(params.process_handle),
response: v2::ProcessKillResponse,
},
#[experimental("process/resizePty")]
/// Resize a running PTY-backed `process/spawn` session by client-supplied `processHandle`.
ProcessResizePty => "process/resizePty" {
params: v2::ProcessResizePtyParams,
serialization: process_handle(params.process_handle),
response: v2::ProcessResizePtyResponse,
},
ConfigRead => "config/read" {
params: v2::ConfigReadParams,
@@ -1401,6 +1435,12 @@ server_notification_definitions! {
PlanDelta => "item/plan/delta" (v2::PlanDeltaNotification),
/// Stream base64-encoded stdout/stderr chunks for a running `command/exec` session.
CommandExecOutputDelta => "command/exec/outputDelta" (v2::CommandExecOutputDeltaNotification),
/// Stream base64-encoded stdout/stderr chunks for a running `process/spawn` session.
#[experimental("process/outputDelta")]
ProcessOutputDelta => "process/outputDelta" (v2::ProcessOutputDeltaNotification),
/// Final exit notification for a `process/spawn` session.
#[experimental("process/exited")]
ProcessExited => "process/exited" (v2::ProcessExitedNotification),
CommandExecutionOutputDelta => "item/commandExecution/outputDelta" (v2::CommandExecutionOutputDeltaNotification),
TerminalInteraction => "item/commandExecution/terminalInteraction" (v2::TerminalInteractionNotification),
/// Deprecated legacy apply_patch output stream notification.