Ejecución de comandos y exec-server
El crate exec-server es el servicio independiente en el que codex ha extraído la ejecución de comandos: escucha una URL (local o remota), recibe ExecParams en estilo JSON-RPC, hace spawn de un subproceso dentro del sandbox y devuelve stdout/stderr por streaming al client. core::exec es la entrada del lado client: calcula el tipo de sandbox, construye el ExecRequest y se lo pasa al exec-server para que lo corra de verdad. El crate exec es un envoltorio fino del comando CLI codex exec.
Responsabilidades
- Proporcionar
run_mainpara arrancar exec-server escuchando en una URL y exponer un telemetry span:codex-rs/exec-server/src/server.rs:17-35 - Definir el trait
ExecBackendy el traitExecProcess, desacoplando «arrancar proceso» de «interactuar con el proceso»:codex-rs/exec-server/src/process.rs:179-208 LocalProcesses el backend por defecto; llama acodex_sandboxing::spawn_processpara levantar el proceso de verdad:codex-rs/exec-server/src/local_process.rs:261-272prepare_exec_requesttraduce el sandbox context a unPreparedExecRequestantes del spawn:codex-rs/exec-server/src/process_sandbox.rs:69-130core::exec::execute_exec_requestes la entrada de llamada del lado client; gestiona timeout, IO cap y clasificación de errores:codex-rs/core/src/exec.rs:437-460
Motivación de diseño
¿Por qué separar exec en un server independiente? Porque la ejecución de comandos tiene que ocurrir dentro del sandbox, y arrancar el sandbox tiene coste por plataforma (init de seatbelt, setup de bubblewrap, preparación de token de Windows). Dejar que exec-server sea un proceso persistente que reutilice esos recursos ya preparados permite al client enviar solo el RPC. Además, en entornos remotos (SSH / sandbox en la nube) se puede usar el mismo protocolo: EnvironmentProvider abstrae lo local y lo remoto, y al client no le importa en qué máquina corre el proceso.
¿Por qué ExecBackend es un trait y no un tipo concreto? Porque LocalProcess es la implementación por defecto, pero en tests se puede reemplazar por un backend mock; en entornos remotos RemoteProcess va por un noise relay. El trait ExecProcess saca «las operaciones tras arrancar el proceso» (read / write / signal / terminate) como algo separado de «cómo arrancarlo».
prepare_exec_request tiene que hacerse en el lado server y no en el client, porque el cwd y los workspace_roots del sandbox context son rutas locales del executor y necesitan conversión native_path. El permission profile además necesita materialize_project_roots_with_workspace_roots para mapear las raíces de proyecto que llegan remotas al sistema de archivos del executor.
Archivos clave
codex-rs/exec-server/src/lib.rs:36-100— entrada del crate, re-exportaExecServerClient,Environment,ExecBackend, etc.codex-rs/exec-server/src/server.rs:17-35—run_main/run_main_with_telemetryarranca la escucha.codex-rs/exec-server/src/process.rs:179-208— los dos traitsExecProcessyExecBackend.codex-rs/exec-server/src/local_process.rs:261-272—LocalProcessllama aspawn_processpara levantar el subproceso.codex-rs/exec-server/src/local_process.rs:610-614— impl deExecBackend for LocalProcess.codex-rs/exec-server/src/process_sandbox.rs:34-67—PreparedExecRequestyPreparedWindowsSandboxRequest.codex-rs/exec-server/src/process_sandbox.rs:69-130—prepare_exec_requestlleva el sandbox context a rutas locales del executor.codex-rs/core/src/exec.rs:117-130—select_process_exec_tool_sandbox_typedel lado core.codex-rs/core/src/exec.rs:350-413—build_exec_requestllama aSandboxManager::transformpara obtener elSandboxExecRequest.
El trait ExecProcess saca «las operaciones tras arrancar el proceso» como algo aparte; read/write/signal/terminate son todos async, desacoplados de «cómo arrancar».
pub trait ExecProcess: Send + Sync {
fn process_id(&self) -> &ProcessId;
fn subscribe_wake(&self) -> watch::Receiver<u64>;
fn subscribe_events(&self) -> ExecProcessEventReceiver;
fn read(&self, after_seq: Option<u64>, max_bytes: Option<usize>, wait_ms: Option<u64>) -> ExecProcessFuture<'_, ReadResponse>;
fn write(&self, chunk: Vec<u8>) -> ExecProcessFuture<'_, WriteResponse>;
fn signal(&self, signal: ProcessSignal) -> ExecProcessFuture<'_, ()>;
fn terminate(&self) -> ExecProcessFuture<'_, ()>;
}LocalProcess es el backend por defecto y llama a codex_sandboxing::spawn_process para levantar el proceso:
let spawned_result = codex_sandboxing::spawn_process(codex_sandboxing::SpawnRequest {
command: &prepared.command,
cwd: prepared.cwd.as_path(),
env: &prepared.env,
arg0: &prepared.arg0,
sandbox: prepared.sandbox,
windows_sandbox: prepared.windows_sandbox_spawn_request(),
tty: params.tty,
stdin_open: params.tty || params.pipe_stdin,
inherited_fds: &[],
}).await;En el lado core, build_exec_request calcula el SandboxType y luego le pasa el SandboxCommand a SandboxManager::transform, obteniendo un SandboxExecRequest con el argv ya envuelto:
let sandbox_type = select_process_exec_tool_sandbox_type(
&file_system_sandbox_policy,
network_sandbox_policy,
windows_sandbox_level,
enforce_managed_network,
);
let manager = SandboxManager::new();
let exec_req = manager
.transform(SandboxTransformRequest {
command, permissions: permission_profile, sandbox: sandbox_type,
/* ... */
})
.map(|request| { /* ...ExecRequest::from_sandbox_exec_request */ })
.map_err(CodexErr::from)?;Flujo de datos
Bordes y fallos
- En
prepare_exec_request, sisandbox_contextesNone, degenera directamente aSandboxType::Noney salta toda la preparación del sandbox:codex-rs/exec-server/src/process_sandbox.rs:80-90 - Si
LocalProcessfalla al arrancar, hay que limpiar el placeholderStartingdel mapprocesses, evitando fugas:codex-rs/exec-server/src/local_process.rs:273-285 - Tras el exec, core usa
is_likely_sandbox_deniedpara mirar stderr y decidir si lo bloqueó el sandbox, devolviendo al modelo un error legible:codex-rs/core/src/exec.rs:804-815 - La salida de un exec tiene un límite duro
EXEC_OUTPUT_MAX_BYTES, para que el subproceso no reviente el stdout con OOM:codex-rs/core/src/exec.rs:72-76 - El drain de IO tiene un timeout de 2s; si tras matar el subproceso un grandchild sigue teniendo el pipe abierto, no deja al agent colgado:
codex-rs/core/src/exec.rs:82-89
Resumen
exec-server separa la ejecución de comandos de core; el trait ExecBackend hace que local y remoto compartan el mismo protocolo RPC. El lado core solo calcula el sandbox, construye el request y recoge el output; el backend por defecto LocalProcess va por codex_sandboxing::spawn_process para levantar el proceso de verdad. Para rastrear «por qué no arrancó el comando» o «por qué se truncó la salida», se lee junto con Sandbox multiplataforma y Clasificación de comandos (execpolicy).