Skip to content

Unified Exec创建进程

从 exec_command 请求追踪 Unified Exec 的环境准备、沙箱尝试、TTY/pipe 选择、本地或远程启动与早退收尾。

基于rust-v0.150.0
CodexRustExecution

Unified Exec创建进程 ​

Codex执行体系总览区分了 handler、runtime、manager 和 backend,Unified Exec数据结构解释了进程对象与 store 的所有权。本篇沿一条真实调用链回答更具体的问题:exec_command 的命令何时变成 ExecRequest,环境变量和 shell snapshot 在哪里生效,沙箱拒绝如何触发重试,TTY 与 pipe 如何选择,以及进程已经退出时为什么仍要先建立输出 task。

本文面向理解 Rust async 的读者。范围截止到 UnifiedExecProcess 创建完成;不展开审批器内部状态、head-tail 截断和 exec-server 消息字段。读完后,你应能从 ExecCommandHandler::handle_call 走到 spawn_process 或 backend.start,并能解释启动前失败、启动后非零退出,以及“本地环境为何仍可能经过 exec-server”这三个问题。

1. 请求成形 ​

1.1 参数归一化 ​

handler 先选择环境和 cwd,再解析 shell、TTY、等待时间、权限和 hook 输入。统一 process ID 在真正 spawn 前就分配,因此任何后续错误都必须释放它。

源码位置:codex-rs/core/src/tools/handlers/unified_exec/exec_command.rs :: ExecCommandHandler::handle_call

rust
let manager: &UnifiedExecProcessManager = &session.services.unified_exec_manager;
let context = UnifiedExecContext::new(
    session.clone(),
    step_context.clone(),
    cancellation_token,
    call_id.clone(),
);
let environment_args: ExecCommandEnvironmentArgs = parse_arguments(&arguments)?;
let Some(turn_environment) = resolve_tool_environment(
    &step_context.environments,
    environment_args.environment_id.as_deref(),
)? else {
    return Err(FunctionCallError::RespondToModel(
        "unified exec is unavailable in this session".to_string(),
    ));
};
let native_environment_cwd = turn_environment.cwd().clone();
let cwd = environment_args
    .workdir
    .as_deref()
    .filter(|workdir| !workdir.is_empty())
    .map_or_else(
        || Ok(native_environment_cwd.clone()),
        |workdir| native_environment_cwd.join(workdir),
    )
    .map_err(|err| FunctionCallError::RespondToModel(err.to_string()))?;
let process_id = manager.allocate_process_id().await;

这里的 cwd 仍是 PathUri,相对路径相对于选定 environment,而不是 Core 进程 cwd。process_id 只是统一会话索引;远程启动时可能再生成 exec-server 内部 ID。

1.2 权限分支 ​

解析出的 sandbox 权限要经过当前 Turn 已授予权限合并和 approval policy 检查。拒绝发生在 spawn 前,故 handler 释放 ID;apply_patch 拦截也发生在 spawn 前,命令可能根本不会创建进程。

源码位置:codex-rs/core/src/tools/handlers/unified_exec/exec_command.rs :: normalized_additional_permissions

rust
let normalized_additional_permissions = match implicit_granted_permissions(
    sandbox_permissions,
    requested_additional_permissions.as_ref(),
    &effective_additional_permissions,
)
.map_or_else(
    || {
        normalize_and_validate_additional_permissions(
            additional_permissions_allowed,
            context.step_context.turn.approval_policy(),
            effective_additional_permissions.sandbox_permissions,
            effective_additional_permissions.additional_permissions,
            effective_additional_permissions.permissions_preapproved,
            permission_cwd,
        )
    },
    |permissions| Ok(Some(permissions)),
) {
    Ok(normalized) => normalized,
    Err(err) => {
        manager.release_process_id(process_id).await;
        return Err(FunctionCallError::RespondToModel(err));
    }
};

这段代码的边界很重要:权限规范化失败不是 child 的 exit code,也不会进入 ProcessState;它是没有进程的请求错误。

2. 运行尝试 ​

2.1 环境与策略 ​

manager 的 open_session_with_sandbox 从选定 TurnEnvironment 取得 shell policy,注入 thread、session、apply-patch 和 permission profile 变量,再删除不能泄漏到子进程的输出通道变量。随后它同时准备 exec-server 环境策略与 ShellSnapshotV2 请求。此时只完成“准备运行尝试”,尚未创建进程。

源码位置:codex-rs/core/src/unified_exec/process_manager.rs :: open_session_with_sandbox

rust
let turn = &context.step_context.turn;
let shell_environment_policy = request.turn_environment.shell_environment_policy();
let local_policy_env = create_env(shell_environment_policy, /*thread_id*/ None);
let mut env = local_policy_env.clone();
env.insert(
    CODEX_THREAD_ID_ENV_VAR.to_string(),
    context.session.thread_id.to_string(),
);
inject_session_id_env(&mut env, context.session.session_id());
inject_apply_patch_env(&mut env, &turn.config.features);
let active_permission_profile = request.turn_environment.active_permission_profile();
inject_permission_profile_env(&mut env, active_permission_profile.as_ref());
let mut env = apply_unified_exec_env(env);
strip_output_env(&mut env);
let mut explicit_env_overrides = shell_environment_policy.r#set.clone();
strip_output_env(&mut explicit_env_overrides);
let exec_server_env_config = ExecServerEnvConfig {
    policy: exec_env_policy_from_shell_policy(shell_environment_policy),
    local_policy_env,
};
let shell_snapshot = shell_snapshot_request(request, &cwd, context);

统一环境固定 NO_COLOR、TERM、LANG、pager 和 CODEX_CI 等值;exec-server 还收到一份 inherit/exclude/set/include-only policy 与 local overlay,用于执行端重建环境,而不是把宿主完整环境无条件复制过去。permission profile、apply-patch 行尾控制和 plugin metrics 输出变量会从用户可配置 policy 中排除,防止配置覆盖运行时通道。

2.2 Orchestrator接缝 ​

manager 构造 UnifiedExecToolRequest 后调用 ToolOrchestrator::run。审批、sandbox 选择、必要时的 retry 都在 orchestrator/runtime 契约内完成;manager 不在这一步直接调用操作系统。

源码位置:codex-rs/core/src/unified_exec/process_manager.rs :: open_session_with_sandbox

rust
let mut orchestrator = ToolOrchestrator::new();
let mut runtime = UnifiedExecRuntime::new(self, request.shell_mode.clone());
let tool_ctx = ToolCtx {
    session: context.session.clone(),
    step_context: Arc::clone(&context.step_context),
    cancellation_token: context.cancellation_token.clone(),
    call_id: context.call_id.clone(),
    tool_name: ToolName::plain("exec_command"),
};
orchestrator
    .run(&mut runtime, &req, &tool_ctx, turn, turn.approval_policy())
    .await
    .map(|result| (result.output, result.deferred_network_approval))

Runtime 的输出现在是 UnifiedExecAttempt,其中包含进程和可选 plugin metrics sidecar;Orchestrator 额外返回 deferred network approval。两类附属资源都会跟随进程生命周期,但完成方式不同:网络审批要在退出或拒绝后结束,metrics sidecar 要用最终 exit code 收尾。

3. 命令变换 ​

3.1 双快照路径 ​

当前 Unified Exec 同时支持两类快照。legacy host snapshot 是 TurnEnvironment 中已经准备好的本地文件,Runtime 可把它包装进 shell 命令;ShellSnapshotV2 则只创建一个协议请求,由 exec-server 在真正执行环境中捕获、缓存和恢复 shell 状态。

ShellSnapshotV2 只有在 feature、executor capability、环境默认 cwd、Direct mode、POSIX shell 与 -lc 命令全部满足时才启用。

源码位置:codex-rs/core/src/unified_exec/shell_snapshot.rs :: shell_snapshot_request

rust
if !context.session.features().enabled(Feature::ShellSnapshotV2)
    || !request.turn_environment.shell_snapshot_v2_supported
    || request.turn_environment.selection.cwd != *cwd
    || !matches!(request.shell_mode, UnifiedExecShellMode::Direct)
    || !matches!(
        request.shell_type,
        ShellType::Bash | ShellType::Zsh | ShellType::Sh
    )
    || request.command.get(1).is_none_or(|flag| flag != "-lc")
{
    return None;
}

Some(ShellSnapshotRequest {
    scope_id: format!(
        "{}:{}",
        context.session.thread_id(),
        request.turn_environment.selection.environment_id
    ),
    shell: ShellInfo {
        name: request.shell_type.name().to_string(),
        path: request.command.first()?.clone(),
    },
})

Runtime 仍会查找 legacy host snapshot,但远程环境不会读取宿主文件:

源码位置:codex-rs/core/src/tools/runtimes/unified_exec.rs :: UnifiedExecRuntime::run

rust
let environment_is_remote = req.turn_environment.environment.is_remote();
let shell_snapshot_location = if environment_is_remote {
    None
} else {
    let native_cwd = req
        .cwd
        .to_abs_path()
        .map_err(|err| ToolError::Rejected(err.to_string()))?;
    req.turn_environment.shell_snapshot(&native_cwd)
};

两条路径可能同时出现在 Runtime 代码中,但不会承担同一职责。shell_snapshot_location 参与宿主命令包装;req.shell_snapshot 被传给 exec-server。后者存在时,即使 environment 是本地,也会改变后面的 backend 选择。

3.2 环境与网络 ​

runtime 从 SandboxAttempt 得到文件系统权限,再决定网络代理是本地 sandbox context、远程 launch config 还是无网络。远程代理若需要 executor-local launch,还必须查询 exec-server capability;能力不足会在 spawn 前返回拒绝。

源码位置:codex-rs/core/src/tools/runtimes/unified_exec.rs :: UnifiedExecRuntime::run

rust
let (file_system_sandbox_policy, _) = attempt.permissions.to_runtime_permissions();
let launch_sandbox_permissions = sandbox_permissions_preserving_denied_reads(
    req.sandbox_permissions,
    &file_system_sandbox_policy,
);
let managed_network = attempt.network_proxy(managed_network_for_sandbox_permissions(
    req.network.as_ref(),
    launch_sandbox_permissions,
));
let env = exec_env_for_sandbox_permissions(&req.env, launch_sandbox_permissions);

3.3 插件附属资源 ​

在命令包装前,Runtime 会识别插件命令并创建 metrics sidecar。sidecar 一方面向 env 注入测量输出位置,另一方面可能要求额外文件权限;这些权限必须与工具请求的 additional permissions 合并后再交给 sandbox。

源码位置:codex-rs/core/src/tools/runtimes/unified_exec.rs :: UnifiedExecRuntime::run

rust
let metrics_sidecar = sidecar_for_command(
    ctx,
    &req.command,
    &req.cwd,
    req.turn_environment.environment.as_ref(),
)
.await;
if let Some(sidecar) = metrics_sidecar.as_ref() {
    sidecar.install_output_env(&mut env);
}

let sidecar_permissions = metrics_sidecar
    .as_ref()
    .map(PluginMetricsSidecar::additional_permissions);
let additional_permissions = merge_permission_profiles(
    req.additional_permissions.as_ref(),
    sidecar_permissions.as_ref(),
);

sidecar 不在 spawn 成功时完成,因为此时没有最终 exit code。Runtime 将它和进程一起放进 UnifiedExecAttempt,后续由首次结果路径或后台 watcher 消费。

3.4 shell命令 ​

本地命令可能经过 shell snapshot、runtime PATH prepend、PowerShell UTF-8 前缀和 elevated sandbox 的 profile 禁用;远程只复制基础 argv。这个顺序解释了“源码里看到的命令”和“OS 最终收到的 argv”可能不同。

源码位置:codex-rs/core/src/tools/runtimes/unified_exec.rs :: UnifiedExecRuntime::run

rust
let mut command = if environment_is_remote {
    base_command.to_vec()
} else {
    maybe_wrap_shell_lc_with_snapshot(
        base_command,
        shell,
        shell_snapshot_location.as_ref(),
        &explicit_env_overrides,
        &env,
        &runtime_path_prepends,
    )
};
if req.shell_snapshot.is_some() {
    let exports =
        runtime_path_prepends.shell_exports_after_snapshot(&explicit_env_overrides);
    if !exports.is_empty()
        && let Some(script) = command.get_mut(2)
    {
        *script = format!("{exports}\n{script}");
    }
}
let command = disable_powershell_profile_for_elevated_windows_sandbox(
    &command,
    Some(&req.shell_type),
    attempt.sandbox_requested,
    attempt.windows_sandbox_level,
);
let command = if matches!(req.shell_type, ShellType::PowerShell) {
    prefix_powershell_script_with_utf8(&command)
} else {
    command
};

ShellSnapshotV2 捕获的是启动环境,Runtime 的 package/zsh-fork PATH prepend 和显式覆盖必须在恢复后重新导出,才能维持本次命令的优先级。PowerShell 则不走 V2,请求仍需要 UTF-8 前缀和 elevated sandbox profile 处理。

如果 shell mode 是 zsh-fork,runtime 会先尝试专用准备路径;条件不满足则记录 warning 并回退到 direct execution。专用路径返回 PreparedUnifiedExecSpawn,其中 SpawnLifecycleHandle 负责 inherited fd 与 escalation session 的启动后清理。

4. 选择执行面 ​

4.1 SandboxCommand ​

runtime 把最终命令、cwd、env、managed network 和 additional permissions 封装成 SandboxCommand,再创建 ExecOptions。这一步把“要运行什么”与“如何审批/沙箱执行”重新合并成可运行请求。

源码位置:codex-rs/core/src/tools/runtimes/unified_exec.rs :: build_unified_exec_sandbox_command

rust
let (program, args) = command
    .split_first()
    .ok_or_else(|| ToolError::Rejected("command args are empty".to_string()))?;
Ok(SandboxCommand {
    program: program.clone().into(),
    args: args.to_vec(),
    cwd: cwd.clone(),
    env: env.clone(),
    managed_network,
    additional_permissions,
})

空 argv 在这里被拒绝,尚未进入 spawn;这和 child 已启动后返回非零 exit code 是两类不同故障。

4.2 ExecRequest成形 ​

open_session_with_exec_env 决定使用宿主 sandbox transform,还是生成 executor-native 请求。判断条件不是单纯的 environment.is_remote():ShellSnapshotV2 也要求 exec-server 在执行端捕获 shell,因此本地环境携带 snapshot 时同样调用 env_for_exec_server。

源码位置:codex-rs/core/src/unified_exec/process_manager.rs :: open_session_with_exec_env

rust
let mut request = if environment.is_remote() || shell_snapshot.is_some() {
    attempt.env_for_exec_server(command, options)
} else {
    attempt.env_for(command, options, network, environment_id)
}
.map_err(ToolError::Codex)?;
let network_policy_decider = network_proxy_launch
    .as_ref()
    .filter(|launch| launch.policy_decision_timeout_ms.is_some())
    .and_then(|_| network.and_then(NetworkProxy::remote_policy_decider));
if environment.is_remote() && network.is_some() && network_proxy_launch.is_none() {
    request.exec_server_enforce_managed_network = false;
}
request.exec_server_network_proxy = network_proxy_launch;
request.exec_server_env_config = exec_server_env_config;
request.exec_server_shell_snapshot = shell_snapshot;

这一步之后的 ExecRequest 已经包含本次 sandbox attempt 的具体权限、环境、网络和 executor 附件;它不再是模型参数。远程环境存在网络对象但没有 executor-local proxy launch 时,Core 明确关闭 exec-server 的 managed-network enforcement,避免远端误判自己持有代理。

4.3 本地分支 ​

只有环境非远程且没有 exec-server shell snapshot 时,manager 才将 PathUri 转为宿主 native cwd,并把 SpawnRequest 交给 sandboxing crate。tty 选择 PTY;当前 Unified Exec 的直接路径把 stdin_open 设为 tty,因此非 TTY 使用无 stdin pipe,中断由统一进程接口另行处理。

源码位置:codex-rs/core/src/unified_exec/process_manager.rs :: open_session_with_prepared_exec_env

rust
let native_cwd = request
    .cwd
    .to_abs_path()
    .map_err(|_| UnifiedExecError::ForeignPath {
        path: request.cwd.clone(),
    })?;
if request.command.is_empty() {
    return Err(UnifiedExecError::MissingCommandLine);
}
let spawn_result = codex_sandboxing::spawn_process(codex_sandboxing::SpawnRequest {
    command: &request.command,
    cwd: native_cwd.as_path(),
    env: &request.env,
    arg0: &request.arg0,
    sandbox: request.sandbox,
    windows_sandbox,
    tty,
    stdin_open: tty,
    inherited_fds: &inherited_fds,
})
.await;

4.4 ExecServer分支 ​

远程环境或带 ShellSnapshotV2 的本地环境从 Environment 取得 exec backend,将请求转换为 ExecParams,再调用 start 或带 network policy decider 的 start_with_network_policy_decider。该分支拒绝 inherited fds,因为这些 fd 属于 Core 宿主进程,不能跨 executor 边界传递。

源码位置:codex-rs/core/src/unified_exec/process_manager.rs :: open_session_with_prepared_exec_env

rust
if environment.is_remote() || request.exec_server_shell_snapshot.is_some() {
    if !inherited_fds.is_empty() {
        return Err(UnifiedExecError::create_process(
            "remote exec-server does not support inherited file descriptors".to_string(),
        ));
    }
    let backend = environment.get_exec_backend();
    let params = exec_server_params_for_request(
        process_id,
        request,
        windows_sandbox_proxy_settings_mode,
        tty,
    );
    let started = match network_policy_decider {
        Some(decider) => {
            backend
                .start_with_network_policy_decider(params, decider)
                .await
        }
        None => backend.start(params).await,
    }
    .map_err(|err| UnifiedExecError::create_process(err.to_string()))?;
    spawn_lifecycle.after_spawn();
    return UnifiedExecProcess::from_exec_server_started(started).await;
}

exec_server_params_for_request 会保留 PathUri、env policy、snapshot、sandbox 和网络字段。sandbox retry 或 snapshot-backed launch 会给 backend process ID 追加 UUID,因为同一 Unified Exec ID 可能对应 executor 尚未清理的上一次启动;模型仍只看到原统一 ID。

5. 进程接管 ​

5.1 本地输出 task ​

UnifiedExecProcess::from_spawned 先拆出 session、stdout、stderr 和 exit receiver,合并两个输出流,创建统一对象并启动 local output task。然后检查 exit receiver:已经退出、channel closed、150ms 内早退和仍存活分别走不同路径。

源码位置:codex-rs/core/src/unified_exec/process.rs :: UnifiedExecProcess::from_spawned

rust
let output_rx = codex_utils_pty::combine_output_receivers(stdout_rx, stderr_rx);
let mut managed = Self::new(
    ProcessHandle::Local(Box::new(process_handle)),
    sandbox_type,
    Some(spawn_lifecycle),
);
managed.output_task = Some(Self::spawn_local_output_task(
    output_rx,
    managed.output_handles().clone(),
    managed.output_tx.clone(),
));

match exit_rx.try_recv() {
    Ok(exit_code) => {
        managed.signal_exit(Some(exit_code));
        managed.check_for_sandbox_denial().await?;
        return Ok(managed);
    }
    Err(TryRecvError::Closed) => {
        managed.signal_exit(None);
        managed.check_for_sandbox_denial().await?;
        return Ok(managed);
    }
    Err(TryRecvError::Empty) => {}
}

先建立 output task 再检查早退,保证极短命令的 stdout/stderr 不会因为 reader 尚未接管而丢失。150ms grace period 用来把“刚启动就退出”纳入统一状态,而不是误判成未启动。

5.2 ExecServer输出 ​

exec-server 分支用 from_exec_server_started 创建 ExecServer handle,并启动消费后端事件的 output task;旧 peer 不报告 sandbox type 时使用 None,避免把未知信息推断成某个具体 sandbox。这里同样覆盖本地 snapshot-backed launch,不能再把 ExecServer handle 直接翻译为“远程进程”。

源码位置:codex-rs/core/src/unified_exec/process.rs :: UnifiedExecProcess::from_exec_server_started

rust
let process_handle = ProcessHandle::ExecServer(Arc::clone(&started.process));
let sandbox_type = started.sandbox_type.unwrap_or(SandboxType::None);
let mut managed = Self::new(process_handle, sandbox_type, None);
let output_handles = managed.output_handles().clone();
managed.output_task = Some(Self::spawn_exec_server_output_task(
    started,
    output_handles,
    managed.output_tx.clone(),
    managed.state_tx.clone(),
));

5.3 Attempt移交 ​

Runtime 返回的不是裸进程,而是 UnifiedExecAttempt。manager 在进入首次输出等待前拆出进程与 metrics sidecar;活进程入 store 时,sidecar 一同交给 exit watcher,短命令则由当前调用在得到 exit code 后完成测量。

源码位置:codex-rs/core/src/unified_exec/process_manager.rs :: UnifiedExecProcessManager::exec_command

rust
let UnifiedExecAttempt {
    process,
    metrics_sidecar,
} = attempt;
let process = Arc::new(process);

let process_started_alive = !process.has_exited() && process.exit_code().is_none();

这个结构说明“进程创建成功”仍不是启动阶段的全部结果。附属测量资源、deferred network approval、输出 transcript 和 watcher 都要在首次 yield 前建立明确所有权。

5.4 早退与失败 ​

若 spawn 返回后进程已经退出,manager 会在首轮结果中携带 exit code,并释放 process ID;若进程仍活跃,exec_command 才把 Arc<UnifiedExecProcess> 放入 store。启动失败则在 manager 返回错误前释放 ID,不会留下可供 write_stdin 使用的半成品条目。

6. 验证路径 ​

6.1 cwd与TTY ​

Core 集成测试开启 Unified Exec feature,分别提交相对 workdir、显式 workdir、默认 pipe 和显式 TTY 命令,断言输出中的 cwd、TTY 行为和事件结果。它证明入口参数确实影响 spawn 请求,不证明所有 shell 或平台 PTY 都相同。

源码位置:codex-rs/core/tests/suite/unified_exec.rs :: unified_exec_resolves_relative_workdir、unified_exec_defaults_to_pipe、unified_exec_can_enable_tty

text
cd codex-rs
RUST_MIN_STACK=8388608 cargo test -p codex-core --test all unified_exec_resolves_relative_workdir -- --test-threads=1
RUST_MIN_STACK=8388608 cargo test -p codex-core --test all unified_exec_defaults_to_pipe -- --test-threads=1
RUST_MIN_STACK=8388608 cargo test -p codex-core --test all unified_exec_can_enable_tty -- --test-threads=1

6.2 早退与远程后端 ​

unified_exec_respects_early_exit_notifications 验证短命令的早退事件;exec-server 的 exec_process_starts_and_exits::remote 使用真实远程测试 transport,断言启动、退出码和关闭事件。前者证明 reader 接管前的退出处理,后者证明远程 backend 的基本进程协议,不外推网络断连恢复或远端 sandbox 能力。

源码位置:

  • codex-rs/core/tests/suite/unified_exec.rs :: unified_exec_respects_early_exit_notifications
  • codex-rs/exec-server/tests/exec_process.rs :: exec_process_starts_and_exits
text
cd codex-rs
RUST_MIN_STACK=8388608 cargo test -p codex-core --test all unified_exec_respects_early_exit_notifications -- --test-threads=1
cargo test -p codex-exec-server --test exec_process 'exec_process_starts_and_exits::remote' -- --test-threads=1

Core 内另有 unified_exec_uses_remote_exec_server_when_configured 和 remote_exec_server_rejects_inherited_fd_launches,但它们要求外部 remote test environment;当前环境执行时按条件自跳过,因此本文不把它们计入通过数量。inherited fd 拒绝结论来自当前分支源码,尚未在该外部 fixture 上实测。

6.3 SnapshotV2 ​

ShellSnapshotV2 测试分别运行本地和远程 bash env 场景:两次命令只捕获一次 profile,恢复函数与过滤后的变量,runtime PATH 覆盖位于 profile PATH 之前,且不会在 codex_home 创建 snapshot 文件。失败回退测试则先让 profile 捕获失败,断言命令按原 argv 执行;修复 profile 并越过 backoff 后,下一次捕获恢复成功。

源码位置:

  • codex-rs/exec-server/tests/exec_process.rs :: shell_snapshot_v2_filters_profile_exports_and_stays_in_memory
  • codex-rs/exec-server/tests/exec_process.rs :: shell_snapshot_v2_capture_failure_falls_back_and_retries
text
cd codex-rs
cargo test -p codex-exec-server --test exec_process 'shell_snapshot_v2_filters_profile_exports_and_stays_in_memory::local_bash_env' -- --test-threads=1
cargo test -p codex-exec-server --test exec_process 'shell_snapshot_v2_filters_profile_exports_and_stays_in_memory::remote_bash_env' -- --test-threads=1
cargo test -p codex-exec-server --test exec_process 'shell_snapshot_v2_capture_failure_falls_back_and_retries::local_pipe_recovery' -- --test-threads=1

7. 源码追踪 ​

遇到“命令没有 process_id”时,从 handler 的 ID 分配点开始,检查权限规范化、apply_patch 拦截、orchestrator 错误和 manager 的 backend 分支;遇到“本地环境却出现 exec-server ID”时,检查是否生成 ShellSnapshotV2 request;遇到“命令输出为空”时,检查统一进程是否先创建 output task、早退是否在 150ms 内发生,以及 sandbox denial 是否把输出改写成错误结果。

text
rg -n "allocate_process_id|release_process_id|open_session_with_sandbox" codex-rs/core/src
rg -n "shell_snapshot_request|UnifiedExecRuntime::run|build_unified_exec_sandbox_command" codex-rs/core/src
rg -n "open_session_with_exec_env|open_session_with_prepared_exec_env" codex-rs/core/src/unified_exec
rg -n "from_spawned|from_exec_server_started|EARLY_EXIT_GRACE_PERIOD" codex-rs/core/src/unified_exec

本篇的主线可以压缩为:先分配 ID,再形成带 StepContext、环境和策略的运行请求;Runtime 合并 snapshot、网络、插件权限和 shell 变换;manager 根据“远程或 snapshot-backed”选择 exec-server,否则直接 spawn;进程对象先接管输出,再决定短命令结束还是进入 store。下一篇将把 Running、Exited、Reaped 等会话状态和 ID 生命周期单独展开。