Skip to content

Exécution de commande et exec-server

源码版本rust-v0.145.0

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

  1. Fournir run_main pour 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
  2. Définir les traits ExecBackend et ExecProcess, pour découpler « lancer un processus » et « interagir avec un processus » : codex-rs/exec-server/src/process.rs:179-208
  3. LocalProcess est le backend par défaut ; il appelle codex_sandboxing::spawn_process pour lancer le processus : codex-rs/exec-server/src/local_process.rs:261-272
  4. prepare_exec_request traduit le sandbox context en PreparedExecRequest avant le spawn : codex-rs/exec-server/src/process_sandbox.rs:69-130
  5. core::exec::execute_exec_request est 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

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 ».

rust
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 :

rust
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é :

rust
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

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.