コマンド実行と exec-server
exec-server crate は codex がコマンド実行を切り出した独立サービスだ:URL(ローカルまたはリモート)を listen し、JSON-RPC 形式の ExecParams を受け取り、sandbox 内で子プロセスを spawn し、stdout/stderr をクライアントに流し戻す。core::exec はクライアント側入口で、sandbox タイプの算出、ExecRequest の構築を担い、exec-server に実際の実行を任せる。exec crate は codex exec CLI コマンドの薄いラッパーだ。
責務
run_mainで exec-server を起動し URL を listen し、telemetry span を露出する:codex-rs/exec-server/src/server.rs:17-35ExecBackendtrait とExecProcesstrait を定義し、「プロセスの起動」と「プロセスとのやり取り」を疎結合にする:codex-rs/exec-server/src/process.rs:179-208LocalProcessはデフォルト backend で、codex_sandboxing::spawn_processを呼んで実際にプロセスを起動する:codex-rs/exec-server/src/local_process.rs:261-272prepare_exec_requestが spawn 前に sandbox context をPreparedExecRequestに翻訳する:codex-rs/exec-server/src/process_sandbox.rs:69-130core::exec::execute_exec_requestはクライアント側呼び出し入口で、タイムアウト、IO 上限、エラー分類を担う:codex-rs/core/src/exec.rs:437-460
設計動機
なぜ exec を独立 server に分けるのか?コマンド実行は sandbox 内部で行う必要があり、sandbox 起動にはプラットフォームごとのオーバーヘッド(seatbelt init、bubblewrap setup、Windows token 準備)があるからだ。exec-server を長駐プロセスにして準備済みのリソースを再利用し、クライアントは RPC を送るだけで済む。同時に、リモート環境(SSH / クラウドサンドボックス)も同じプロトコルを使える——EnvironmentProvider がローカルとリモートを抽象化し、クライアントはプロセスが実際にどのマシンで走るかを気にしなくてよい。
なぜ ExecBackend が具体型ではなく trait なのか?LocalProcess はデフォルト実装だが、テスト時には mock backend に差し替え可能。リモート環境では RemoteProcess が noise relay を通す。ExecProcess trait が「プロセス起動後の操作」(read / write / signal / terminate)を独立させ、「起動方法」と切り離す。
prepare_exec_request がクライアント側ではなくサーバ側で行われなければならないのは、sandbox context の cwd、workspace_roots が executor のローカルパスであり、native_path 変換が必要だからだ。permission profile は materialize_project_roots_with_workspace_roots でリモートから送られたプロジェクトルートを executor のファイルシステムにマッピングする。
主要ファイル
codex-rs/exec-server/src/lib.rs:36-100— crate 入口。ExecServerClient、Environment、ExecBackendなどを re-export。codex-rs/exec-server/src/server.rs:17-35—run_main/run_main_with_telemetryが listen を起動。codex-rs/exec-server/src/process.rs:179-208—ExecProcessとExecBackendの二つの trait。codex-rs/exec-server/src/local_process.rs:261-272—LocalProcessがspawn_processを呼んで子プロセスを起動。codex-rs/exec-server/src/local_process.rs:610-614—ExecBackend for LocalProcessの impl。codex-rs/exec-server/src/process_sandbox.rs:34-67—PreparedExecRequestとPreparedWindowsSandboxRequest。codex-rs/exec-server/src/process_sandbox.rs:69-130—prepare_exec_requestが sandbox context を executor のローカルパスに落とす。codex-rs/core/src/exec.rs:117-130— core 側select_process_exec_tool_sandbox_type。codex-rs/core/src/exec.rs:350-413—build_exec_requestがSandboxManager::transformを呼んでSandboxExecRequestを得る。
ExecProcess trait は「プロセス起動後の操作」を独立させ、read/write/signal/terminate はすべて非同期で、「起動方法」と切り離す。
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 はデフォルト backend で、codex_sandboxing::spawn_process を呼んで実際にプロセスを起動する:
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;core 側 build_exec_request は SandboxType を算出した後、SandboxCommand を SandboxManager::transform に渡し、wrapper argv 付きの SandboxExecRequest を得る:
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)?;データフロー
境界と失敗
prepare_exec_requestでsandbox_contextがNoneの場合はSandboxType::Noneに退化し、すべてのサンドボックス準備をスキップする:codex-rs/exec-server/src/process_sandbox.rs:80-90LocalProcess起動失敗時はprocessesmap からStartingプレースホルダを消し、リークを防ぐ:codex-rs/exec-server/src/local_process.rs:273-285- core は exec 完成後に
is_likely_sandbox_deniedで stderr を見て sandbox にブロックされたかを判断し、モデルに読みやすいエラーを返す:codex-rs/core/src/exec.rs:804-815 - 一回の exec 出力には
EXEC_OUTPUT_MAX_BYTESの硬い上限があり、子プロセスが stdout を流し込んで OOM になるのを防ぐ:codex-rs/core/src/exec.rs:72-76 - IO drain には 2s のタイムアウトがあり、子プロセスが kill された後も孫プロセスが pipe を握って agent が永遠に止まるのを防ぐ:
codex-rs/core/src/exec.rs:82-89
まとめ
exec-server はコマンド実行を core から独立させ、ExecBackend trait でローカルとリモート環境が同じ RPC プロトコルを共有する。core 側は sandbox 算出、request 構築、output 受取だけを担い、LocalProcess デフォルト backend が codex_sandboxing::spawn_process を呼んで実際にプロセスを起動する。「なぜコマンドが起動しないのか」「なぜ出力が切り詰められたのか」の現場を追うには クロスプラットフォームサンドボックス と コマンド分類 execpolicy を併せて参照。