# Codex Cloud、代码审查与 worktree

> 三个功能都以 git 为底座：codex cloud 把云端任务的 diff 用 git apply --3way 落到本地；/review 派出一个受限的一次性子代理，自己跑 git diff 找问题，结果以 JSON 回到主对话；worktree 用 git worktree 给并行的对话各开一份检出。本篇讲它们的数据流、边界约束，以及共用的 codex-git-utils。

- 作者：David（道雾轩）
- 专栏：Codex 源码解读（https://daiw.net/manual/codex-source.md）
- 最后更新：2026-09-29
- 原文：https://daiw.net/manual/codex-source/cloud-and-review
- 转载与引用：请注明出处并附原文链接（https://daiw.net/about/copyright）

# Codex Cloud、代码审查与 worktree

这一篇的三个功能看起来不相干，底下却是同一件东西：git。云端任务的产物是一份 diff，要应用回本地仓库；代码审查的输入是一份 diff，要从 git 里找出来；worktree 更直接，就是 `git worktree`。仓库里负责和 git 打交道的是 `codex-git-utils`（打补丁、查分支与提交、判定信任根目录）与 `codex-worktree`（托管 worktree）。

## 怎么用

- **Codex Cloud**：`codex cloud` 打开一个浏览云端任务的终端界面；子命令 `exec` 提交任务，`status`、`list`、`diff` 查看，`apply` 把某次尝试的 diff 应用到本地。另有顶层的 `codex apply <TASK_ID>`（别名 `a`）。
- **代码审查**：交互模式里 `/review` 选择审查未提交改动、对比某个基准分支、某个提交或自定义要求；非交互用 `codex review --uncommitted`、`--base`、`--commit` 或直接给出审查要求。
- **worktree**：`--worktree` 或 `/worktree` 在托管的 worktree 里开对话，`/worktree` 还能浏览与删除。

细节分别见手册的[代码审查](https://daiw.net/manual/codex/review)与 [Codex Cloud、GitHub 与 IDE](https://daiw.net/manual/codex/cloud-integrations)。

## Codex Cloud：CLI 里的云端任务

云端任务由 `codex-cloud-tasks` 这个 crate 提供，界面、子命令都在里面；和后端说话的部分抽成了 `codex-cloud-tasks-client` 里的一个 trait：

```rust
pub trait CloudBackend: Send + Sync {
    fn list_tasks<'a>(
        &'a self,
        env: Option<&'a str>,
        limit: Option<i64>,
        cursor: Option<&'a str>,
    ) -> CloudBackendFuture<'a, TaskListPage>;
    fn get_task_summary(&self, id: TaskId) -> CloudBackendFuture<'_, TaskSummary>;
    fn get_task_diff(&self, id: TaskId) -> CloudBackendFuture<'_, Option<String>>;
    // ...
    /// Dry-run apply (preflight) that validates whether the patch would apply cleanly.
    /// Never modifies the working tree. When `diff_override` is supplied, the provided diff is
    /// used instead of re-fetching the task details so callers can apply alternate attempts.
    fn apply_task_preflight(
        &self,
        id: TaskId,
        diff_override: Option<String>,
    ) -> CloudBackendFuture<'_, ApplyOutcome>;
    // ...
    fn create_task<'a>(
        &'a self,
        env_id: &'a str,
        prompt: &'a str,
        git_ref: &'a str,
        qa_mode: bool,
        best_of_n: usize,
    ) -> CloudBackendFuture<'a, CreatedTask>;
}
```

（`codex-rs/cloud-tasks-client/src/api.rs:136`）

真实实现是走 HTTP 的 `HttpClient`，另有一个 `codex-cloud-tasks-mock-client`，只在 debug 构建里设 `CODEX_CLOUD_TASKS_MODE=mock` 时启用，方便离线开发界面。后端地址默认 `https://chatgpt.com/backend-api`（可用 `CODEX_CLOUD_TASKS_BASE_URL` 覆盖），此时路径是 `/wham/tasks/...` 这一套；认证必须是走 Codex 后端的 ChatGPT 类凭据，否则直接提示先 `codex login`。

几个值得注意的边界：

- **提交任务只传分支名**。`create_task` 的参数是环境 ID、提示词、`git_ref` 和尝试次数，没有 diff。`git_ref` 依次取 `--branch`、当前分支、默认分支，都没有就用 `main`，所以本地没推送的改动不会被带上云端。`--attempts` 让云端跑 1 到 4 次尝试（best-of-N），`apply` 与 `diff` 可以用 `--attempt` 挑其中一次。
- **环境自动识别**只认 GitHub：从本地 git 远端解析出 `owner/repo`，请求 `/wham/environments/by-repo/github/{owner}/{repo}`。
- **应用分两步**。先做预检，再真正应用，都落到 `codex-git-utils` 的 `apply_git_patch`：把 diff 写进临时文件，预检跑 `git apply --check`，应用跑 `git apply --3way`；按退出码和输出里解析出的已应用、跳过、冲突路径，把结果归为 `Success`、`Partial`、`Error`。`apply_git_patch` 调用 git 时都带上 `-c safe.bareRepository=explicit`，拒绝隐式发现的裸仓库。顶层的 `codex apply` 属于另一个小 crate `codex-chatgpt`，它按任务 ID 取回任务、找出其中的 PR 产物 diff，同样交给 `apply_git_patch`。
- **诊断日志写在当前目录**。`codex cloud` 的启动信息、认证方式与账号 ID、应用失败的详情，都以追加方式写进相对路径 `error.log`（`codex-rs/cloud-tasks/src/util.rs:18`），也就是你运行命令时所在的目录。

## 代码审查：一个受限的一次性子代理

`/review` 的界面部分很薄：选“基准分支”时列出本地分支，选“提交”时列出最近 100 个提交（`recent_commits`），最后都变成一个 `ReviewTarget`，经 app-server 的 `review/start` 送进内核。真正的活在 `codex-rs/core/src/tasks/review.rs`：

```mermaid
sequenceDiagram
  participant U as 终端界面或 codex review
  participant AS as app-server
  participant P as 当前线程
  participant R as 审查子代理
  U->>AS: review/start，带审查目标
  AS->>P: resolve_review_request 生成审查指令
  P->>R: run_codex_thread_one_shot，换上审查提示词
  R->>R: 自己跑 git diff、读文件、查证
  R-->>P: 过程事件照常转发，最后一条助手消息扣下
  R-->>P: TurnComplete，最后一条消息是 JSON
  P->>P: 解析成 ReviewOutputEvent
  P->>P: exit_review_mode，结果写回对话历史
  P-->>U: ExitedReviewMode 条目
```

第一步把目标翻译成一句审查指令，而不是把 diff 塞进提示词：审查未提交改动就说“审查暂存、未暂存与未跟踪的文件”；对比基准分支时，先在本地算出 `HEAD` 与该分支（远端那份更新时取远端）的 merge base，指令里直接写“运行 `git diff <sha>`”，算不出来才退而教它怎么从上游找 merge base（`codex-rs/prompts/src/review_request.rs:59`）。diff 由审查者自己用工具取，大改动不会一次撑爆上下文。

第二步派出子代理，它是当前配置的一份受限副本：

```rust
    let config = ctx.config.clone();
    let mut sub_agent_config = config.as_ref().clone();
    // Carry over review-only feature restrictions so the delegate cannot
    // re-enable blocked tools (web search, collab tools, view image).
    if let Err(err) = sub_agent_config
        .web_search_mode
        .set(WebSearchMode::Disabled)
    {
        panic!("by construction Constrained<WebSearchMode> must always support Disabled: {err}");
    }
    let _ = sub_agent_config.features.disable(Feature::Collab);
    let _ = sub_agent_config.features.disable(Feature::MultiAgentV2);

    // Set explicit review rubric for the sub-agent
    sub_agent_config.base_instructions = Some(crate::REVIEW_PROMPT.to_string());
    sub_agent_config.base_instructions_provenance = Some(BaseInstructionsProvenance::Custom);
    sub_agent_config.permissions.approval_policy = Constrained::allow_only(AskForApproval::Never);

    let model = config
        .review_model
        .clone()
        .unwrap_or_else(|| ctx.model_info().slug.clone());
    sub_agent_config.model = Some(model);
```

（`codex-rs/core/src/tasks/review.rs:105`）

网页搜索关掉、子代理协作关掉、审批固定为 `never`——审查者只读、只跑命令，不会停下来问你，也不会再派生子代理；沙箱沿用当前会话的设置。模型用 `review_model`，没配就用当前模型。基础指令换成 `rubric.md`：它规定什么才算值得指出的问题（由这次改动引入、作者知道了会修、不靠臆测），每条带 `[P0]` 到 `[P3]` 的优先级、置信度和与 diff 重叠的代码位置，最后给出 `patch is correct` 或 `patch is incorrect` 的总体结论，并要求只输出 JSON、不要顺手写修复补丁。

<Callout type="info">

  上面那段注释说要挡住的工具包括 view image，但 v0.158.0 的这段代码只关了网页搜索和两个协作特性，`view_image` 由 `Feature::ViewImage` 控制，审查时并没有被关掉。

</Callout>

第三步收尾。子代理的过程事件（命令执行等）照常转给前端；助手消息晚一步转发，流式增量不转，最后一条则扣下不发。一轮结束时取这最后一条解析成 `ReviewOutputEvent`：先整体按 JSON 解析，不行就截取第一个 `{` 到最后一个 `}` 再试，还不行就把原文放进 `overall_explanation` 兜底。`exit_review_mode` 随后往当前线程的历史里写入两条消息：一条按 `exit_success.xml` 模板渲染的用户消息，列出审查发现；一条助手消息给出可读的审查结果。审查因此成了对话的一部分，接下来一句“把 P1 修了”就能接着干。审查中途被打断，则写入 `exit_interrupted.xml` 的内容，提示重新运行。

## worktree：托管的并行检出

托管 worktree 由 `codex-worktree` 的 `WorktreeManager` 负责，目录布局与桌面端共用：根目录默认是 `$CODEX_HOME/worktrees`（`config.toml` 里 `[desktop]` 的 `git-worktree-root` 可以改），下面先是一个 4 位十六进制的桶（取随机 UUID 的前 4 位），再是仓库名；macOS 上还会在根目录写一个 `.metadata_never_index`，不让 Spotlight 去索引。创建过程是这样的：

```rust
        let result = git_output(
            &source_root,
            GitOperation::WorkingTree,
            [
                OsStr::new("worktree"),
                OsStr::new("add"),
                OsStr::new("--detach"),
                OsStr::new("--no-checkout"),
                root.as_os_str(),
                OsStr::new(&head_sha),
            ],
        );
        // ...
            // Discover destination-only filters before materializing files, and pin
            // the working tree even when per-worktree configuration is disabled.
            git_output(
                &root,
                GitOperation::WorkingTree,
                [
                    "--work-tree=.",
                    "reset",
                    "--hard",
                    "--no-recurse-submodules",
                    head_sha.as_str(),
                ],
            )
```

（`codex-rs/worktree/src/lib.rs:88`）

先把基准（默认 `HEAD`，可指定）解析成确切的提交，再以分离 HEAD、不检出文件的方式挂上 worktree；接着只在新 worktree 自己的 `config.worktree` 里写 `core.worktree`，不改源仓库的共享配置；最后才 `reset --hard` 把文件铺出来。任何一步失败都回滚，删掉半成品。对话的工作目录映射到 worktree 里同样的相对子目录，映射出界也会回滚。

每个 worktree 的 git 元数据目录里可以放一份 `codex-thread.json`，记录它属于哪个线程，同一个 worktree 不能被第二个线程占用。列出时不只看 `git worktree list`，还逐个核对目录布局、`.git` 文件和指回来的 `gitdir`，防止路径被别的检出冒名顶替。删除更谨慎：不能删当前所在的 worktree，里面有被忽略的本地文件就拒绝，调用 `git worktree remove` 时不加 `--force`，有未提交改动时交给 git 拒绝。自动清理（默认保留 15 个）是桌面端的设置，CLI 共用同一个根目录，但 `WorktreeSettings::for_cli` 明确关掉了自动清理。

项目信任与 worktree 的关系也值得一提：`resolve_root_git_project_for_trust` 会顺着 worktree 的 `.git` 文件找到主仓库根目录，并且只用文件系统操作、不调用 `git`，因此信任判定以主仓库为准，在远端[执行环境](https://daiw.net/manual/codex-source/exec-server)上也能用。

## 和《从 LLM 到 Coding Agent》对照

审查这件事几乎就是[子 Agent](https://daiw.net/manual/llm-to-agent/subagents) 那篇的标准用法：一个工具内部跑一个完整的 agent 循环，细节留在子上下文里，只把结论带回主线程。Codex 的增量在“受限”与“结构化”：子代理的能力被收窄，结论必须是可解析的 JSON，而且结论会写进主对话，变成下一轮的上下文。

worktree 可以对照 [Grok Build 的并行 worktree](https://daiw.net/manual/grok-build/fast-worktree)。两边都先 `git worktree add --no-checkout`，再自己填工作树；不同的是 grok 用写时复制把源目录的文件拷过去，服务于并行的子代理，销毁前把改动快照进 git ref，Codex 则从一个确定的提交 `reset --hard` 出干净副本，服务于并行的对话，删除时有改动就拒绝。

---

上一篇：[遥测与分析 · OpenTelemetry、埋点与反馈](https://daiw.net/manual/codex-source/telemetry) · 下一篇：[工程实践 · 测试、构建与发布](https://daiw.net/manual/codex-source/engineering)
