Codex Cloud、代码审查与 worktree
三个功能都以 git 为底座:codex cloud 把云端任务的 diff 用 git apply --3way 落到本地;/review 派出一个受限的一次性子代理,自己跑 git diff 找问题,结果以 JSON 回到主对话;worktree 用 git worktree 给并行的对话各开一份检出。本篇讲它们的数据流、边界约束,以及共用的 codex-git-utils。
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还能浏览与删除。
细节分别见手册的代码审查与 Codex Cloud、GitHub 与 IDE。
Codex Cloud:CLI 里的云端任务
云端任务由 codex-cloud-tasks 这个 crate 提供,界面、子命令都在里面;和后端说话的部分抽成了 codex-cloud-tasks-client 里的一个 trait:
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属于另一个小 cratecodex-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:
第一步把目标翻译成一句审查指令,而不是把 diff 塞进提示词:审查未提交改动就说“审查暂存、未暂存与未跟踪的文件”;对比基准分支时,先在本地算出 HEAD 与该分支(远端那份更新时取远端)的 merge base,指令里直接写“运行 git diff <sha>”,算不出来才退而教它怎么从上游找 merge base(codex-rs/prompts/src/review_request.rs:59)。diff 由审查者自己用工具取,大改动不会一次撑爆上下文。
第二步派出子代理,它是当前配置的一份受限副本:
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、不要顺手写修复补丁。
上面那段注释说要挡住的工具包括 view image,但 v0.158.0 的这段代码只关了网页搜索和两个协作特性,view_image 由 Feature::ViewImage 控制,审查时并没有被关掉。
第三步收尾。子代理的过程事件(命令执行等)照常转给前端;助手消息晚一步转发,流式增量不转,最后一条则扣下不发。一轮结束时取这最后一条解析成 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 去索引。创建过程是这样的:
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,因此信任判定以主仓库为准,在远端执行环境上也能用。
和《从 LLM 到 Coding Agent》对照
审查这件事几乎就是子 Agent 那篇的标准用法:一个工具内部跑一个完整的 agent 循环,细节留在子上下文里,只把结论带回主线程。Codex 的增量在“受限”与“结构化”:子代理的能力被收窄,结论必须是可解析的 JSON,而且结论会写进主对话,变成下一轮的上下文。
worktree 可以对照 Grok Build 的并行 worktree。两边都先 git worktree add --no-checkout,再自己填工作树;不同的是 grok 用写时复制把源目录的文件拷过去,服务于并行的子代理,销毁前把改动快照进 git ref,Codex 则从一个确定的提交 reset --hard 出干净副本,服务于并行的对话,删除时有改动就拒绝。
上一篇:遥测与分析 · OpenTelemetry、埋点与反馈 · 下一篇:工程实践 · 测试、构建与发布