Exécution de commande et exec-server
Le crate exec-server est le service indépendant que codex a extrait pour l'exécution des commandes : il écoute sur une URL (locale ou distante), reçoit des ExecParams au format JSON-RPC, spawn un sous-processus dans le sandbox et renvoie stdout/stderr au client. core::exec est l'entrée côté client : elle calcule le type de sandbox, construit un ExecRequest, puis confie l'exécution réelle à exec-server. Le crate exec est une fine enveloppe de la commande CLI codex exec.
Responsabilités
- Fournir
run_mainpour démarrer exec-server à l'écoute d'une URL, avec un span de télémétrie :codex-rs/exec-server/src/server.rs:17-35 - Définir les traits
ExecBackendetExecProcess, pour découpler « lancer un processus » et « interagir avec un processus » :codex-rs/exec-server/src/process.rs:179-208 LocalProcessest le backend par défaut ; il appellecodex_sandboxing::spawn_processpour lancer le processus :codex-rs/exec-server/src/local_process.rs:261-272prepare_exec_requesttraduit le sandbox context enPreparedExecRequestavant le spawn :codex-rs/exec-server/src/process_sandbox.rs:69-130core::exec::execute_exec_requestest l'entrée côté client ; elle gère timeout, cap d'IO et classification d'erreur :codex-rs/core/src/exec.rs:437-460
Motivations de conception
Pourquoi avoir séparé exec en un server dédié ? Parce que l'exécution de commande doit se faire à l'intérieur du sandbox, et que le démarrage d'un sandbox a un coût plateforme (init seatbelt, setup bubblewrap, préparation du token Windows). Un exec-server comme processus long-running réutilise ces ressources préparées ; le client ne fait qu'envoyer un RPC. Parallèlement, un environnement distant (SSH / cloud sandbox) peut utiliser le même protocole — EnvironmentProvider abstrait local et distant, le client n'a pas à se soucier de la machine où tourne réellement le processus.
Pourquoi ExecBackend est-il un trait plutôt qu'un type concret ? Parce que LocalProcess est l'impl par défaut, mais en test on peut le substituer par un mock backend ; en environnement distant, RemoteProcess passe par un noise relay. Le trait ExecProcess extrait « les opérations une fois le processus lancé » (read / write / signal / terminate) indépendamment de « comment le lancer ».
prepare_exec_request doit s'exécuter côté server et non côté client, car cwd et workspace_roots dans le sandbox context sont des chemins locaux à l'exécteur, et nécessitent une conversion native_path. Le permission profile appelle aussi materialize_project_roots_with_workspace_roots pour mapper les racines projet reçues du distant vers le système de fichiers de l'exécuteur.
Fichiers clés
codex-rs/exec-server/src/lib.rs:36-100— entrée du crate, re-exporteExecServerClient,Environment,ExecBackend, etc.codex-rs/exec-server/src/server.rs:17-35—run_main/run_main_with_telemetrydémarrent l'écoute.codex-rs/exec-server/src/process.rs:179-208— les deux traitsExecProcessetExecBackend.codex-rs/exec-server/src/local_process.rs:261-272—LocalProcessappellespawn_processpour lancer le sous-processus.codex-rs/exec-server/src/local_process.rs:610-614— implExecBackend for LocalProcess.codex-rs/exec-server/src/process_sandbox.rs:34-67—PreparedExecRequestetPreparedWindowsSandboxRequest.codex-rs/exec-server/src/process_sandbox.rs:69-130—prepare_exec_requestramène le sandbox context aux chemins locaux de l'exécuteur.codex-rs/core/src/exec.rs:117-130—select_process_exec_tool_sandbox_typecôté core.codex-rs/core/src/exec.rs:350-413—build_exec_requestappelleSandboxManager::transformpour obtenir unSandboxExecRequest.
Le trait ExecProcess extrait « les opérations une fois le processus lancé » : read/write/signal/terminate sont tous async, découplés de « comment le lancer ».
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 est le backend par défaut, qui appelle codex_sandboxing::spawn_process pour lancer le processus :
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;Côté core, build_exec_request calcule le SandboxType puis confie le SandboxCommand à SandboxManager::transform, pour obtenir un SandboxExecRequest avec un argv emballé :
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)?;Flux de données
Limites et échecs
- Dans
prepare_exec_request, sisandbox_contextvautNone, on dégénère enSandboxType::Noneet on saute toute préparation sandbox :codex-rs/exec-server/src/process_sandbox.rs:80-90 - En cas d'échec de démarrage,
LocalProcessdoit nettoyer l'emplacementStartingdans la mapprocessespour éviter une fuite :codex-rs/exec-server/src/local_process.rs:273-285 - Après exécution, core inspecte stderr avec
is_likely_sandbox_deniedpour savoir si le sandbox a bloqué, et renvoie une erreur lisible au modèle :codex-rs/core/src/exec.rs:804-815 - La sortie d'un exec a une limite dure
EXEC_OUTPUT_MAX_BYTES, pour éviter qu'un sous-processus ne sature stdout en OOM :codex-rs/core/src/exec.rs:72-76 - Le drain IO a un timeout de 2 s ; même si un grandchild garde le pipe ouvert après le kill du sous-processus, l'agent ne reste pas bloqué indéfiniment :
codex-rs/core/src/exec.rs:82-89
Récapitulatif
exec-server extrait l'exécution de commande de core ; le trait ExecBackend fait que local et distant partagent le même protocole RPC. Côté core, on ne fait que calculer le sandbox, construire la request et collecter la sortie ; LocalProcess, backend par défaut, passe par codex_sandboxing::spawn_process pour lancer le processus. Pour remonter « pourquoi la commande n'a pas tourné » ou « pourquoi la sortie a été tronquée », à lire en parallèle avec Bac à sable multiplateforme et Classification execpolicy.