Befehlsausführung und exec-server
Der exec-server-Crate ist der von codex ausgelagerte eigenständige Dienst für Befehlsausführung: Er lauscht an einer URL (lokal oder remote), nimmt JSON-RPC-artige ExecParams entgegen, spawned in der Sandbox einen Kindprozess und streamt stdout/stderr an den Client zurück. core::exec ist der clientseitige Einstieg, der den Sandbox-Typ berechnet, eine ExecRequest aufbaut und dem exec-server die eigentliche Ausführung übergibt. Der exec-Crate ist der dünne Wrapper für das CLI-Kommando codex exec.
Verantwortlichkeiten
run_mainbietet den Start des exec-server an, der an einer URL lauscht und einen Telemetrie-Span exponiert:codex-rs/exec-server/src/server.rs:17-35ExecBackend-Trait undExecProcess-Trait entkoppeln „Prozess starten" und „mit dem Prozess interagieren":codex-rs/exec-server/src/process.rs:179-208LocalProcessist der Standard-Backend und ruftcodex_sandboxing::spawn_processauf, um den Prozess zu starten:codex-rs/exec-server/src/local_process.rs:261-272prepare_exec_requestübersetzt den Sandbox-Kontext vor dem Spawn in einenPreparedExecRequest:codex-rs/exec-server/src/process_sandbox.rs:69-130core::exec::execute_exec_requestist der clientseitige Aufrufeinstieg für Timeout, IO-Cap und Fehlerklassifikation:codex-rs/core/src/exec.rs:437-460
Entwurfsbeweggründe
Warum exec als eigener Server auskoppeln? Weil die Befehlsausführung innerhalb der Sandbox stattfinden muss und das Starten der Sandbox plattformspezifische Kosten verursacht (seatbelt init, bubblewrap setup, Windows token preparation). Lässt man exec-server als langlebigen Prozess diese vorbereiteten Ressourcen wiederverwenden, schickt der Client nur ein RPC. Zugleich können Remote-Umgebungen (SSH / Cloud-Sandbox) dasselbe Protokoll nutzen — EnvironmentProvider abstrahiert lokal/remote, und der Client muss nicht wissen, auf welcher Maschine der Prozess tatsächlich läuft.
Warum ist ExecBackend ein Trait und kein konkreter Typ? Weil LocalProcess die Standard-Implementierung ist, aber im Test ein Mock-Backend eingesetzt werden kann; in Remote-Umgebungen läuft RemoteProcess über ein Noise-Relay. Das ExecProcess-Trait koppelt „Operationen nach dem Start des Prozesses" (read / write / signal / terminate) von „wie starten" ab.
prepare_exec_request muss server- und nicht clientseitig laufen, weil cwd und workspace_roots im Sandbox-Kontext lokale Pfade des Executors sind und über native_path übersetzt werden müssen. Das permission profile muss außerdem mit materialize_project_roots_with_workspace_roots die remote hereinkommenden Projektwurzeln auf das Dateisystem des Executors mappen.
Wichtige Dateien
codex-rs/exec-server/src/lib.rs:36-100— Crate-Einstieg, re-exportiertExecServerClient,Environment,ExecBackendusw.codex-rs/exec-server/src/server.rs:17-35—run_main/run_main_with_telemetrystarten den Listener.codex-rs/exec-server/src/process.rs:179-208— Die beiden TraitsExecProcessundExecBackend.codex-rs/exec-server/src/local_process.rs:261-272—LocalProcessruftspawn_processauf, um den Kindprozess zu starten.codex-rs/exec-server/src/local_process.rs:610-614—ExecBackend for LocalProcess-impl.codex-rs/exec-server/src/process_sandbox.rs:34-67—PreparedExecRequestundPreparedWindowsSandboxRequest.codex-rs/exec-server/src/process_sandbox.rs:69-130—prepare_exec_requestübersetzt den Sandbox-Kontext in lokale Executor-Pfade.codex-rs/core/src/exec.rs:117-130—select_process_exec_tool_sandbox_typeauf core-Seite.codex-rs/core/src/exec.rs:350-413—build_exec_requestruftSandboxManager::transformauf und liefertSandboxExecRequest.
Das ExecProcess-Trait koppelt „Operationen nach dem Start des Prozesses" ab; read/write/signal/terminate sind alles asynchrone Operationen, unabhängig von „wie starten".
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 ist der Standard-Backend und ruft codex_sandboxing::spawn_process auf, um den Prozess zu starten:
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;Auf core-Seite berechnet build_exec_request den SandboxType und übergibt die SandboxCommand an SandboxManager::transform, um den SandboxExecRequest mit Wrapper-argv zu erhalten:
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)?;Datenfluss
Grenzen und Fehler
- Ist
sandbox_contextinprepare_exec_requestNone, fällt es direkt aufSandboxType::Nonezurück und überspringt die gesamte Sandbox-Vorbereitung:codex-rs/exec-server/src/process_sandbox.rs:80-90 - Schlägt der Start von
LocalProcessfehl, muss derStarting-Platzhalter aus derprocesses-Map entfernt werden, um Lecks zu vermeiden:codex-rs/exec-server/src/local_process.rs:273-285 - Nach Abschluss liest core per
is_likely_sandbox_denieddie stderr, um zu erkennen, ob die Sandbox blockiert hat, und gibt dem Modell eine lesbare Fehlermeldung:codex-rs/core/src/exec.rs:804-815 - Ein einzelner exec-Output hat ein hartes Limit
EXEC_OUTPUT_MAX_BYTES, damit der Kindprozess den stdout-Buffer nicht vollstopft und OOM auslöst:codex-rs/core/src/exec.rs:72-76 - Der IO-Drain hat ein 2s-Timeout; hält ein grandchild nach dem Kill noch die Pipe offen, blockiert das den Agenten nicht dauerhaft:
codex-rs/core/src/exec.rs:82-89
Zusammenfassung
exec-server koppelt die Befehlsausführung aus core aus; der ExecBackend-Trait teilt sich eine RPC-Protokoll-Schicht zwischen lokaler und Remote-Umgebung. Auf core-Seite wird nur die Sandbox berechnet, der Request gebaut und der Output eingesammelt; der Standard-Backend LocalProcess ruft codex_sandboxing::spawn_process auf, um den Prozess zu starten. Um „warum ein Befehl nicht anlief" oder „warum der Output abgeschnitten ist" zu verfolgen, siehe gemeinsam mit Plattformübergreifende Sandbox und Befehlsklassifikation execpolicy.