☰
openrig:用YAML+tmux编排Claude Code与Codex的AI编程工作台
2026/10/1 1:11:29 网站建设 项目流程

1. 从 openrig 这个名字说起:它到底想解决什么问题

第一次看到openrig这个词,我脑子里蹦出来的不是某个具体工具,而是一种"把散装零件拼成一台能跑的机器"的画面。rig 在英文里本来就有"装配、搭建"的意思,比如把一台电脑的各个部件装起来叫 rig,把一套实验设备搭起来也叫 rig。前面加个 open,意思就很明确了:这是一套开放的、可组装的配置骨架,用来把 Claude Code、Codex 这类命令行 AI 编程助手,和 YAML 配置、tmux 会话管理这些东西串成一条顺手的流水线。

我之所以对这个方向特别有感触,是因为过去大半年我几乎每天都在和 Claude Code、Codex 这两个工具打交道。刚开始用的时候,我的工作流是这样的:打开一个终端,敲claude,聊几句,关掉;再开一个终端,敲codex,聊几句,关掉。项目一多,配置文件散落在各个角落,~/.claude、~/.codex、项目根目录下的各种 yaml,改一个参数要翻半天。更别提有时候想让两个工具同时跑、互相参考对方的输出,结果终端窗口开了一堆,自己都分不清哪个是哪个。

openrig要解决的,恰恰就是这个"散"的问题。它不是某个具体的软件包,而更像一套约定俗成的组织方式:用 YAML 描述配置,用 tmux 管理会话,把 Claude Code 和 Codex 的启动参数、模型接入、上下文策略统一收拢到一份可版本控制的文件里。你换一台机器,把这份 rig 拉下来,几条命令就能还原出几乎一模一样的工作环境。

这篇文章适合谁看?如果你已经在用 Claude Code 或 Codex,但每次配置都靠记忆和手敲,那这篇能帮你把流程固化下来;如果你还没入门,只是想搞清楚这两个工具和 YAML、tmux 之间到底怎么配合,那这篇也能给你一条清晰的路径。我会尽量把每一步的"为什么"讲透,而不是甩一堆命令让你照抄。

提示:本文提到的所有配置思路都基于公开的通用实践,具体参数请以你本地实际安装的版本为准。工具迭代很快,遇到对不上的地方,优先看官方文档。

2. 为什么是 YAML + tmux 这套组合,而不是别的

2.1 YAML 承担的是"声明",不是"执行"

很多人第一次接触 YAML 是在写 CI 流水线或者 Docker Compose 的时候,潜意识里觉得它就是个"配置文件格式"。这个理解没错,但不够准确。YAML 真正的价值在于它把"我想要什么状态"和"怎么达到这个状态"分开了。你写model: claude-sonnet,你声明的是意图;至于这个意图怎么被解析、怎么被传给底层,那是工具的事。

放到 openrig 这个场景里,YAML 要声明的东西大概有这么几类。第一类是模型接入信息,比如你用的是官方端点还是本地模型,端点地址、模型名称、超时时间这些。第二类是会话行为,比如是否开启自动上下文压缩、历史保留多少轮、是否允许工具调用。第三类是项目级覆盖,不同项目可能有不同的系统提示词、不同的工作目录、不同的忽略规则。

我见过不少人图省事,把这些东西直接写死在 shell 的 alias 里,比如alias cc='claude --model xxx --append-system-prompt "..."'。短时间看没问题,但一旦你要维护三五个项目,alias 就会变成一坨谁也不敢动的意大利面。YAML 的好处是它是数据,不是代码,你可以用脚本去生成它、校验它、diff 它,甚至让 AI 帮你改它。

2.2 tmux 解决的是"会话持久化"和"多路复用"

tmux 这个东西,老运维和服务器玩家都很熟,但很多做 AI 编程的朋友反而没怎么用过。它的核心能力有两个:一是会话可以脱离终端窗口独立存在,你关掉 SSH、关掉终端,里面的进程照样跑;二是可以在一个窗口里切分出多个面板,同时看多个东西。

这两个能力放到 AI 编程场景里简直是量身定做。你想想,Claude Code 跑一个长任务,可能要读几十个文件、改十几处代码,中间你去泡杯咖啡,回来发现终端被误关了,上下文全没了,那种崩溃感谁经历谁知道。用 tmux 起一个会话,tmux new -s work,然后在里面跑 Claude Code,你随时可以tmux detach离开,回来tmux attach -t work接着看,进程一直在。

多路复用就更实用了。我常用的布局是左边一个大面板跑 Claude Code 做主力开发,右边上下分两个小面板,上面跑 Codex 做代码审查或者写测试,下面留一个 shell 用来跑构建和测试命令。三个东西在同一个 tmux 会话里,切换用快捷键,比开三个终端窗口清爽太多。

2.3 为什么不是别的方案

有人会问,用 VS Code 的集成终端不行吗?用 screen 不行吗?用 systemd 或者 nohup 不行吗?

VS Code 集成终端的问题在于它和编辑器窗口绑定,你关掉 VS Code 它就没了,而且多面板管理不如 tmux 灵活。screen 是老前辈,功能上够用,但配置语法和快捷键都不如 tmux 现代,社区活跃度也差一截。nohup 只能解决"后台跑",解决不了"多路复用"和"随时切回去看"。至于 systemd,那是给守护进程用的,交互式会话用它是杀鸡用牛刀。

所以 openrig 选 YAML + tmux 这套组合,本质上是选了"声明式配置"加"持久化多路复用"这两个最贴合 AI 编程工作流的特性。这不是拍脑袋,是踩过坑之后的最优解。

3. 把 openrig 的目录结构搭起来

3.1 一份可版本控制的骨架长什么样

我自己的 openrig 目录大概是这样组织的,你可以根据自己的习惯调整,但核心思路是"分层":全局默认、工具专属、项目覆盖,三层各管各的。

openrig/ ├── README.md ├── global/ │ ├── claude.yaml │ ├── codex.yaml │ └── tmux.conf ├── projects/ │ ├── project-a/ │ │ ├── claude.yaml │ │ └── codex.yaml │ └── project-b/ │ └── claude.yaml └── scripts/ ├── rig-up.sh └── rig-down.sh

global/放的是所有项目通用的默认值,比如你常用的模型、通用的系统提示词、tmux 的快捷键绑定。projects/下面每个子目录对应一个具体项目,只放这个项目需要覆盖的字段。scripts/里放两个入口脚本,一个负责把配置组装起来并启动 tmux 会话,一个负责清理。

这种分层的好处是,改全局默认只动一个文件,改项目特例只动对应目录,不会互相污染。而且整个目录可以直接丢进 git,换机器 clone 下来就能用。

3.2 全局配置里该放什么,不该放什么

全局claude.yaml我一般会放这些字段:

model: claude-sonnet timeout: 120 max_tokens: 8192 system_prompt_file: ./prompts/default.md auto_compact: true compact_threshold: 0.8 tools: allow: - read_file - write_file - run_command deny: - delete_file

这里有几个点值得展开说。auto_compact和compact_threshold是控制上下文压缩的,阈值设成 0.8 意思是当上下文用到 80% 的时候自动触发压缩。这个值我试过 0.6 到 0.9 之间,太低会导致频繁压缩、丢失细节,太高又容易在关键时刻爆掉,0.8 是我用下来比较平衡的点。

tools里的 allow 和 deny 是权限控制。我强烈建议在全局层面就把delete_file这类危险操作禁掉,需要的时候在项目级单独放开。这不是不信任 AI,而是给自己留一道保险。我有个朋友就是没设这个,AI 在重构的时候顺手删了一个他还没提交的目录,虽然最后从 git 找回来了,但那半小时的心跳是真的。

全局codex.yaml结构类似,但 Codex 的配置项和 Claude Code 不完全一样,比如它更强调approval_mode和sandbox这类安全相关的设置。我一般会把approval_mode设成on-request,也就是关键操作要人工确认,避免它在没看清楚的情况下乱改。

3.3 项目级覆盖的写法

项目级的 yaml 不需要写全,只写要覆盖的字段就行。比如 project-a 需要用一个更长的超时和不同的系统提示词:

timeout: 300 system_prompt_file: ./prompts/project-a.md tools: allow: - delete_file

这里tools.allow是追加还是替换,取决于你的合并策略。我自己的脚本用的是"深度合并、数组追加"的策略,也就是项目级允许的会加到全局允许的上面,而不是覆盖掉。这个策略要在脚本里明确实现,不然容易出现"我以为放开了结果没放开"的困惑。

注意:YAML 对缩进极其敏感,用空格不要用 Tab。我见过太多因为一个 Tab 导致整个配置解析失败、排查半天的案例。建议在编辑器里把 Tab 自动转成两个空格。

4. 用 tmux 把 Claude Code 和 Codex 编排到一张工作台上

4.1 会话布局的设计思路

tmux 的布局不是随便切的,要按"信息流"来设计。我的核心原则是:主力工具占最大面积,辅助工具放在视线容易扫到的位置,shell 放在手最容易够到的地方。

具体来说,我常用的布局是:

  • 左侧 60% 宽度,上下分两个面板,上面跑 Claude Code,下面跑 Codex
  • 右侧 40% 宽度,上下分两个面板,上面跑一个长期运行的测试监听,下面留一个自由 shell

为什么把 Claude Code 和 Codex 上下放而不是左右放?因为这两个工具的输出都是长文本,上下排列能让每个面板获得更宽的显示区域,代码行不容易折行,读起来舒服。左右排列的话,每个面板宽度不够,稍微长一点的代码就换行了,看着累。

4.2 用脚本一键拉起整个工作台

手动切面板、起进程太麻烦,我写了个rig-up.sh来干这件事。核心逻辑是:先读配置,把环境变量准备好,然后创建 tmux 会话,按预设布局切分面板,最后在每个面板里发送对应的启动命令。

#!/usr/bin/env bash set -euo pipefail SESSION="openrig" PROJECT="${1:-default}" # 读取全局和项目配置,合并后导出为环境变量 source ./scripts/load-config.sh "$PROJECT" # 如果会话已存在,直接 attach if tmux has-session -t "$SESSION" 2>/dev/null; then tmux attach -t "$SESSION" exit 0 fi # 创建会话,第一个窗口跑 Claude Code tmux new-session -d -s "$SESSION" -n main tmux send-keys -t "$SESSION:main" "claude --config $CLAUDE_CONFIG" C-m # 垂直切分,下半部分跑 Codex tmux split-window -v -t "$SESSION:main" tmux send-keys -t "$SESSION:main.1" "codex --config $CODEX_CONFIG" C-m # 右侧再切一列 tmux split-window -h -t "$SESSION:main" tmux send-keys -t "$SESSION:main.2" "npm run test:watch" C-m tmux split-window -v -t "$SESSION:main.2" tmux send-keys -t "$SESSION:main.3" "clear" C-m # 调整面板大小 tmux resize-pane -t "$SESSION:main.0" -x 60% tmux select-pane -t "$SESSION:main.0" tmux attach -t "$SESSION"

这个脚本里有几个细节值得说。set -euo pipefail是必须的,任何一步出错就停下来,避免半拉子状态。has-session的判断是为了幂等,重复执行不会创建一堆重复会话。send-keys后面的C-m相当于回车,少了它命令只是被输入但不会执行,这个坑我踩过。

load-config.sh负责把 YAML 转成环境变量。我用的方案是用yq解析 YAML,然后按需导出。如果你不想引入额外依赖,也可以用 Python 的pyyaml写个小脚本,效果一样。

4.3 tmux.conf 里几个真正有用的设置

默认的 tmux 配置对新手不太友好,前缀键是Ctrl-b,和很多编辑器的快捷键冲突。我一般会改成Ctrl-a,然后把一些常用操作绑到更顺手的键上。

# 前缀键改成 Ctrl-a set -g prefix C-a unbind C-b bind C-a send-prefix # 开启鼠标支持,方便点选面板和滚动 set -g mouse on # 窗口和面板编号从 1 开始,符合直觉 set -g base-index 1 setw -g pane-base-index 1 # 用 | 和 - 切分面板,比默认的 % 和 " 好记 bind | split-window -h -c "#{pane_current_path}" bind - split-window -v -c "#{pane_current_path}" # 用 h/j/k/l 在面板间移动,和 vim 一致 bind h select-pane -L bind j select-pane -D bind k select-pane -U bind l select-pane -R # 增大历史滚动缓冲 set -g history-limit 50000

history-limit这个设置特别重要。AI 编程工具的输出动辄几百行,默认的 2000 行缓冲很快就不够用了,往上翻看不到之前的输出会非常抓狂。设成 50000 之后基本够用一整天。

mouse on有人喜欢有人讨厌,但我觉得在 AI 编程场景下开着更方便,因为经常需要点选面板、滚动查看长输出。如果你习惯纯键盘操作,关掉也行。

5. 配置合并与加载:那些容易翻车的地方

5.1 深度合并不是简单的字典覆盖

前面提到项目级配置要覆盖全局配置,这个"覆盖"具体怎么实现,坑很多。最简单的做法是dict.update(),但这是浅合并,遇到嵌套字典会直接把整个子字典替换掉。比如全局配置里tools.allow有三个元素,项目级只想加一个,浅合并的结果是项目级那一个把全局三个全顶掉了,这显然不是你要的。

正确的做法是深度合并:遇到字典就递归进去合并,遇到数组就追加(或者按你的策略去重后追加),遇到标量才覆盖。下面是一个 Python 实现的参考:

def deep_merge(base, override): result = base.copy() for key, value in override.items(): if key in result and isinstance(result[key], dict) and isinstance(value, dict): result[key] = deep_merge(result[key], value) elif key in result and isinstance(result[key], list) and isinstance(value, list): # 数组追加并去重,保持顺序 merged = result[key] + [x for x in value if x not in result[key]] result[key] = merged else: result[key] = value return result

这个函数我用了很久,基本能覆盖大部分场景。唯一要注意的是数组去重用的是in判断,如果数组元素是字典,in比的是引用不是内容,会失效。如果你的配置里有字典数组,需要自己写更精细的比较逻辑。

5.2 环境变量和配置文件的优先级

有时候你不想改文件,只想临时换个模型试试,这时候环境变量就派上用场了。我的策略是:环境变量优先级最高,其次是项目级配置,最后是全局配置。加载脚本里按这个顺序依次覆盖。

# 先加载全局 export CLAUDE_MODEL=$(yq '.model' global/claude.yaml) # 再加载项目级 if [ -f "projects/$PROJECT/claude.yaml" ]; then export CLAUDE_MODEL=$(yq ".model // \"$CLAUDE_MODEL\"" "projects/$PROJECT/claude.yaml") fi # 最后环境变量兜底 export CLAUDE_MODEL="${CLAUDE_MODEL_OVERRIDE:-$CLAUDE_MODEL}"

yq的//操作符是"如果左边是 null 就用右边",这个在处理可选字段时很好用。不过要注意yq有多个版本,语法不完全一样,用之前先yq --version确认一下。

5.3 配置校验:别等到运行时才发现写错了

YAML 写错了,最怕的是工具启动到一半才报错,那时候你可能已经等了几十秒。我的做法是在加载脚本里加一道校验,用 JSON Schema 或者简单的必填字段检查,把错误提前暴露出来。

validate_config() { local file="$1" # 检查文件能否被解析 if ! yq '.' "$file" > /dev/null 2>&1; then echo "配置解析失败: $file" return 1 fi # 检查必填字段 local model=$(yq '.model' "$file") if [ "$model" == "null" ] || [ -z "$model" ]; then echo "缺少必填字段 model: $file" return 1 fi return 0 }

这个校验很简陋,但能拦住 80% 的低级错误。如果你追求更严格,可以引入check-jsonschema这类工具,把配置的 schema 定义清楚,校验会更全面。

提示:把校验放在rig-up.sh的最前面,任何配置有问题就直接退出,不要带着错误配置去启动工具。我吃过这个亏,工具跑了一半才发现模型名写错了,白等了好几分钟。

6. 实测中遇到的几个典型问题和处理方式

6.1 会话里的进程被意外终止

tmux 会话虽然持久,但里面的进程不是无敌的。我遇到过几次 Claude Code 跑到一半突然退出,排查下来原因各不相同。有一次是内存不够被系统 OOM 杀了,有一次是网络波动导致 API 调用连续失败触发了工具的自我保护,还有一次纯粹是我自己手滑在面板里按了Ctrl-C。

针对 OOM,我的做法是给 tmux 会话里的进程加一个内存监控,超过阈值就告警。针对网络问题,Claude Code 和 Codex 一般都有重试机制,但重试次数和间隔可以在配置里调,我一般会把重试次数设成 3 到 5 次,间隔用指数退避。针对手滑,这个没辙,只能养成习惯,在主力面板里操作时手放轻点。

6.2 上下文丢失与恢复

AI 编程最怕的就是上下文丢失。你聊了半小时,它已经理解了整个项目的结构,结果会话断了,重新开始它又变成一张白纸。tmux 能保证进程不因为终端关闭而终止,但保证不了进程本身不崩溃。

我的应对策略是双保险。第一,Claude Code 和 Codex 都支持把对话历史落盘,配置里开启persist_history之类的选项,会话恢复时能读回来。第二,我会定期让 AI 把当前的理解和待办事项写到一个CONTEXT.md文件里,万一会话彻底丢了,新会话第一件事就是读这个文件,能快速恢复大部分上下文。

这个CONTEXT.md的写法也有讲究,不要写成一坨流水账,要结构化:当前任务是什么、已经完成了哪些、下一步计划是什么、有哪些已知的坑。我一般让 AI 每完成一个阶段性任务就更新一次,这样即使中途换工具(比如从 Claude Code 换到 Codex),新工具也能快速接手。

6.3 两个工具同时跑时的资源竞争

Claude Code 和 Codex 同时跑,最容易出的问题是文件锁冲突。比如两个工具都想改同一个文件,一个写了一半另一个也来写,结果就是内容错乱。我遇到过最离谱的一次是两个工具互相把对方的修改覆盖了,最后文件内容变成了四不像。

解决办法有两个层面。配置层面,我会在项目级 yaml 里给两个工具划分不同的工作范围,比如 Claude Code 负责src/下的代码,Codex 负责tests/下的测试,井水不犯河水。流程层面,如果确实需要两个工具协作,我会用 tmux 的synchronize-panes功能临时同步输入,让它们看到同样的指令,但输出还是各自独立,避免直接冲突。

# 临时同步所有面板的输入 tmux set-window-option synchronize-panes on # 用完记得关掉 tmux set-window-option synchronize-panes off

这个功能要慎用,开着的时候你在任何一个面板敲的字都会同步到所有面板,很容易误操作。我一般只在需要给两个工具发同样指令的时候开一下,发完立刻关。

6.4 配置漂移:为什么昨天还好好的今天就不行了

配置漂移是长期使用 openrig 最隐蔽的问题。你可能改了一个全局配置,当时没觉得有什么,过了几天发现某个项目的行为不对劲,排查半天才想起来是那次改动的影响。

我的做法是给 openrig 目录上 git,每次改配置都提交,commit message 写清楚改了什么、为什么改。这样出问题的时候git log一翻就知道最近动了什么。更进一步,我写了个rig-diff.sh,对比当前生效的配置和 git 里的版本,有差异就提示。

#!/usr/bin/env bash # 对比当前配置和 git 版本 for f in global/*.yaml projects/*/*.yaml; do if ! git diff --quiet "$f" 2>/dev/null; then echo "配置有未提交的改动: $f" git diff "$f" fi done

这个脚本我放在rig-up.sh的开头,每次启动前跑一下,有未提交的改动就提醒我。不是强制阻止,只是让我心里有数。

7. 把这套东西用顺之后的几点个人体会

用 openrig 这套思路管理 Claude Code 和 Codex 有一段时间了,最大的感受是"确定性"变强了。以前每次开新项目都要重新配一遍,现在 clone 下来改几个字段就能跑,省下来的时间累积起来很可观。而且因为配置是文件、是数据,我可以让 AI 帮我改配置,比如"把超时从 120 改成 300",它直接改 yaml 就行,不用我手敲命令。

另一个体会是 tmux 的价值被严重低估了。很多人觉得它就是个终端复用工具,但在 AI 编程这个场景里,它其实是"工作台的骨架"。你把面板布局设计好,把每个面板的职责定清楚,整个人的工作节奏都会变得不一样。我现在打开电脑第一件事就是rig-up.sh,几秒钟之后一个完整的工作台就位,直接进入状态,不用再花时间搭环境。

最后分享一个小技巧:给 tmux 会话起名字的时候,用项目名而不是固定的openrig。这样你可以同时开多个项目的会话,tmux ls一眼就能看出哪个是哪个,切换用tmux attach -t 项目名,比在一堆无名会话里翻找高效得多。我现在的习惯是每个活跃项目一个会话,不活跃的定期清理,保持列表清爽。

这套东西没有什么高深的技术,核心就是"把重复的事情固化下来"。YAML 负责声明,tmux 负责承载,脚本负责串联,三者各司其职。你不需要一次做到完美,先跑起来,用着用着自然就知道哪里该优化了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询