命令執行與 exec-server
exec-server crate 是 codex 把命令執行抽出來的獨立服務:它監聽一個 URL(本地或遠端),接收 JSON-RPC 風格的 ExecParams,在 sandbox 裡 spawn 子處理程序,並把 stdout/stderr 流回 client。core::exec 是 client 側入口,負責算 sandbox 類型、構造 ExecRequest,再交給 exec-server 實際跑。exec crate 是 codex exec 這個 CLI 命令的薄包裝。
職責
- 提供
run_main啟動 exec-server 監聽 URL,並暴露 telemetry span:codex-rs/exec-server/src/server.rs:17-35 - 定義
ExecBackendtrait 與ExecProcesstrait,把「啟動處理程序」與「和處理程序互動」解耦:codex-rs/exec-server/src/process.rs:179-208 LocalProcess是預設 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是 client 側呼叫入口,負責超時、IO cap、錯誤歸類:codex-rs/core/src/exec.rs:437-460
設計動機
為什麼把 exec 拆成獨立 server?因為命令執行需要在 sandbox 內部進行,而 sandbox 啟動有平台開銷(seatbelt init、bubblewrap setup、Windows token 準備)。讓 exec-server 長駐處理程序重用這些準備好的資源,client 只發 RPC 即可。同時,遠端環境(SSH / 雲沙箱)可以用同一套協議——EnvironmentProvider 抽象本地與遠端,client 不用關心處理程序實際跑在哪台機器上。
為什麼 ExecBackend 是 trait 而非具體型別?因為 LocalProcess 是預設實現,但測試時可以替換成 mock backend;遠端環境下 RemoteProcess 走 noise relay。ExecProcess trait 把「處理程序已經啟動後的操作」(read / write / signal / terminate)獨立出來,與「如何啟動」解耦。
prepare_exec_request 必須在 server 側而非 client 側做,因為 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 入口,re-exportExecServerClient、Environment、ExecBackend等。codex-rs/exec-server/src/server.rs:17-35—run_main/run_main_with_telemetry啟動監聽。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 後 grandchild 還持著 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 一起看。