
1. 从“openrig”说起一个被低估的本地 AI 编码环境编排思路第一次看到“openrig”这个词我脑子里蹦出来的不是某个具体软件而是一种很朴素的工程直觉把散落各处的 AI 编码工具用一套可复现的配置“装配”成一个稳定的本地工作台。rig 在英文里有“装配、搭台子”的意思open 则点明了它的开源、开放属性。所以 openrig 本质上不是一个孤立的命令行工具而是一类做法的统称——围绕 Claude Code、Codex 这类终端里的 AI 编码代理用 YAML 描述环境、用 tmux 维持会话、用本地代理做协议转换最终让“打开终端就能干活”这件事变得可重复、可迁移、可调试。我接触这套东西的契机很实际。早些年大家用 AI 写代码基本是复制粘贴到网页对话框里来回倒腾。后来 Claude Code、Codex CLI 这类工具出来了能在终端里直接读写文件、跑命令、看报错效率一下子不一样了。但问题也跟着来了每换一台机器就要重新装一遍、配一遍模型想从云端换成 LM Studio 或 DeepSeek 的本地/自建端点配置项散落在好几个文件里会话一断上下文全丢多个项目并行时终端窗口开得满屏都是。openrig 这类思路要解决的就是这些“装完能用”和“长期好用”之间的落差。这篇文章适合谁看如果你已经在用或者准备用 Claude Code、Codex CLI并且不满足于“照着教程点一遍”而是想搞清楚为什么这么配、配置之间怎么联动、出问题从哪查那这篇就是写给你的。我会把 openrig 拆成几个可独立理解又互相咬合的部分整体设计思路、YAML 配置的核心细节、tmux 会话编排、本地代理与端点切换、以及一堆我踩过的坑。全程按从业者交流的口吻来不绕弯子。提示本文提到的所有工具、配置均为本地开发环境下的通用实践涉及的具体端点、密钥请以你自己环境的实际情况为准切勿把任何凭据写进会提交到版本库的文件里。2. openrig 的整体设计与思路拆解2.1 为什么是“编排”而不是“安装脚本”很多人第一反应是写个 install.sh把 Claude Code、Codex、依赖一股脑装上就完事了。我一开始也这么干后来发现根本不够用。安装只是起点真正折磨人的是运行时的状态管理Claude Code 要读~/.claude下的配置Codex 有自己的~/.codex目录两者对代理、模型端点、认证方式的要求还不一样。你今天把端点指向云端明天想切到本地 LM Studio改的地方可能有三四处改漏一处就报错。openrig 的核心思路是把“环境”当成一份声明式的描述来管理。你用 YAML 写清楚这个工作台需要哪些工具、每个工具的模型端点是什么、会话怎么组织、环境变量怎么注入。然后由一个轻量的引导流程去读这份描述把实际运行环境装配出来。这样做的好处很直接——配置即文档换机器时把 YAML 带过去就行出问题时配置和实际状态的差异一眼能看出来而不是靠记忆去猜“我上次是不是改过哪个文件”。这里有个关键取舍声明式配置 vs 命令式脚本。脚本灵活但不可逆、难审计YAML 声明式配置可读性强、易 diff但需要你提前想清楚有哪些可变项。openrig 选择后者是因为 AI 编码工具的配置项其实相对固定——无非是模型、端点、认证、会话、工作目录这几类。把它们抽象成 YAML 的字段收益远大于成本。2.2 三个核心组件如何咬合把 openrig 拆开看它其实由三块拼起来缺一不可。第一块是YAML 配置层。它负责描述“要什么”。比如你想让 Claude Code 走本地模型就在配置里写明端点地址、模型名、必要的请求头想让 Codex 走另一套端点就单独给它一段。YAML 的层级结构天然适合表达这种“一个工作台、多个工具、每个工具有自己的参数”的关系。第二块是tmux 会话层。它负责“怎么跑”。AI 编码代理是长驻进程你希望它在后台稳定运行随时能 attach 回去看输出。tmux 的 session/window/pane 模型刚好匹配一个项目一个 session一个 session 里分几个 window 分别跑 Claude Code、Codex、日志监控。会话断了重连上下文还在这对长时间的重构任务太重要了。第三块是本地代理层。它负责“怎么转”。Claude Code 和 Codex 各自说自己的“协议方言”而你想接的模型端点比如 LM Studio 的本地服务、DeepSeek 的兼容接口可能又是另一套格式。中间加一层本地代理做协议转换和端点路由就能让上层工具无感知地切换后端。这也是热词里“cc switch local proxy failed while handling codex endpoint /responses”这类报错的高发区——代理没配对请求格式对不上自然就失败了。这三块的关系可以这样理解YAML 是图纸tmux 是厂房本地代理是传送带。图纸画错了厂房再大也白搭传送带接错了原料进不去。2.3 方案选型背后的几个“为什么不”为什么用 tmux 而不是直接开终端标签页终端标签页是会话级的关掉就没了tmux 是进程级的detach 之后进程继续跑。AI 编码任务动辄几分钟到几十分钟中途你可能会去开会、切窗口tmux 能保证任务不中断。而且 tmux 可以脚本化创建会话配合 openrig 的引导流程一条命令就把整个工作台拉起来。为什么用 YAML 而不是 JSON 或 TOMLJSON 不支持注释配置里想写句“这行是给本地模型用的”都做不到TOML 表达嵌套结构时略显啰嗦。YAML 支持注释、层级清晰、对多行字符串友好写端点地址和请求头很顺手。当然 YAML 的缩进敏感是双刃剑后面讲排查时会专门说这个坑。为什么要在本地加代理不直接改工具源码改源码意味着每次工具升级都要重新 patch维护成本极高。本地代理是外挂式的工具升级不影响代理逻辑代理升级也不影响工具。这种解耦在快速迭代的 AI 工具生态里尤其重要——Claude Code 和 Codex 的版本更新都很频繁能不动源码就不动。3. YAML 配置的核心细节与实操要点3.1 一份 openrig 配置的骨架长什么样先给一份我实际在用的配置骨架字段名你可以按自己习惯调整重点是结构。这份配置描述了一个包含 Claude Code 和 Codex 两个工具、各自指向不同模型端点的工作台。# openrig.yaml version: 1 workspace: name: dev-rig root: ~/projects/myapp tools: claude-code: enabled: true endpoint: http://127.0.0.1:8080/v1 model: local-coder-32b env: ANTHROPIC_BASE_URL: http://127.0.0.1:8080 ANTHROPIC_API_KEY: dummy-key-for-local session: window: claude workdir: ~/projects/myapp codex: enabled: true endpoint: http://127.0.0.1:8081/v1 model: deepseek-coder env: OPENAI_BASE_URL: http://127.0.0.1:8081/v1 OPENAI_API_KEY: dummy-key-for-local session: window: codex workdir: ~/projects/myapp proxy: enabled: true routes: - from: /v1/messages to: /v1/chat/completions target: http://127.0.0.1:1234这份配置里tools下面每个工具都有自己的端点、模型和环境变量proxy段则定义了请求怎么转发。注意ANTHROPIC_API_KEY和OPENAI_API_KEY我填的是占位符——本地模型通常不校验密钥但工具本身要求这个变量存在不填会直接报错退出。这是很多人第一次配本地模型时卡住的地方。3.2 端点、模型名与认证字段的对应关系这里必须把一件事讲透不同工具对“端点”和“密钥”的字段名要求是不一样的配错一个字母就报认证失败或连接失败。Claude Code 认的是ANTHROPIC_BASE_URL和ANTHROPIC_API_KEY。如果你把它指向一个兼容 Anthropic 消息格式的本地服务这两个变量就够了。但如果你指向的是只支持 OpenAI 格式的服务比如 LM Studio 默认的/v1/chat/completions那就需要本地代理把 Anthropic 的/v1/messages请求翻译成 OpenAI 格式这就是上面proxy.routes在做的事。Codex 认的是OPENAI_BASE_URL和OPENAI_API_KEY。它默认走 OpenAI 的 responses 接口如果你接的后端只支持 chat completions同样需要代理做转换。热词里那个 “cc switch local proxy failed while handling codex endpoint /responses” 的报错十有八九就是代理没有正确处理/responses这个路径或者后端根本不支持这个接口。我整理了一张对照表配的时候照着核对能省很多时间工具端点环境变量密钥环境变量默认请求路径常见后端兼容性Claude CodeANTHROPIC_BASE_URLANTHROPIC_API_KEY/v1/messages需 Anthropic 格式或代理转换Codex CLIOPENAI_BASE_URLOPENAI_API_KEY/v1/responses需 OpenAI 格式或代理转换LM Studio自定义通常不需要/v1/chat/completionsOpenAI 兼容DeepSeek 兼容接口自定义自定义/v1/chat/completionsOpenAI 兼容注意本地服务大多不校验密钥但工具会检查变量是否存在。填一个非空的占位字符串即可不要留空也不要填成nullYAML 里null和空字符串是两回事。3.3 YAML 缩进与类型陷阱YAML 最大的坑就是缩进和类型推断。我见过太多次因为一个空格导致整个配置解析失败或者更隐蔽的——解析成功了但值不对。第一个陷阱是缩进必须用空格不能用 Tab。很多编辑器默认 Tab 缩进粘到 YAML 里就炸。建议在编辑器里把 YAML 文件的 Tab 自动转空格打开缩进统一用两个空格。第二个陷阱是布尔值和字符串的混淆。YAML 里yes、no、on、off、true、false都会被解析成布尔值。如果你某个字段本意是字符串比如模型名恰好叫on那就会被解析成true。解决办法是加引号model: on。第三个陷阱是数字被当成字符串或反之。端口号8080是数字没问题但如果你写version: 1.0有些解析器会给你一个浮点数而程序期望的是字符串1.0。这种类型不匹配的报错往往很隐晦排查时优先怀疑类型。第四个陷阱是多行字符串的写法。端点地址一般不长但如果你要写一段 JSON 格式的请求头用|保留换行、用折叠换行效果完全不同。我一般建议复杂结构直接拆成多个字段别硬塞进一个多行字符串里。3.4 配置校验在启动前把错误拦下来openrig 这类编排思路里我最推荐加的一个环节是启动前的配置校验。别等工具跑起来报错了才去查而是在读取 YAML 之后就做一轮检查必填字段在不在、端点地址格式对不对、端口有没有被占用、引用的模型名是否在后端存在。校验逻辑不用很复杂一个简单的脚本就够。核心是检查这几项tools下每个启用的工具其endpoint是否能连通发一个轻量请求探测、env里的密钥变量是否非空、session.workdir是否存在。任何一项不过直接报错退出并打印出具体是哪个工具的哪个字段有问题。这么做的好处是把错误暴露在最容易理解的位置。工具内部的报错往往是“认证失败”“连接超时”这种笼统信息而配置校验能告诉你“codex 的 OPENAI_BASE_URL 指向的 8081 端口没有服务在监听”。排查效率完全不是一个量级。4. tmux 会话编排让 AI 编码任务稳定长跑4.1 一个项目一个 session 的组织方式tmux 的模型是 server 管 sessionsession 管 windowwindow 管 pane。openrig 里我习惯一个项目一个 sessionsession 名就用项目名。这样切换项目时tmux attach -t myapp一下就到不会和别的项目混在一起。一个 session 里通常开三个 window第一个跑 Claude Code第二个跑 Codex第三个跑日志或代理监控。为什么把两个工具分在不同 window 而不是同一 window 分 pane因为 AI 编码代理的输出是滚动的pane 分屏后每个 pane 的可视区域太小看代码 diff 很痛苦。分 window 的话每个工具都能占满整个终端需要对比时再切。创建会话的命令可以写进 openrig 的引导流程里大致是这样# 创建名为 myapp 的 session第一个 window 跑 claude tmux new-session -d -s myapp -n claude -c ~/projects/myapp # 在 session 里新建 window 跑 codex tmux new-window -t myapp -n codex -c ~/projects/myapp # 再建一个 window 跑代理日志 tmux new-window -t myapp -n proxy -c ~/projects/myapp-d是 detached 模式创建完不自动 attach方便脚本继续往下跑。-c指定工作目录这个很重要——AI 编码工具默认在当前目录下读写文件工作目录设错它可能去改你别的项目。4.2 会话持久化与断线重连tmux 最大的价值就是持久化。你tmux detach或者直接关掉终端窗口session 里的进程照跑不误。下次tmux attach -t myapp回来Claude Code 还在那儿上下文没丢。但这里有个细节要注意AI 编码工具本身可能有自己的会话状态。比如 Claude Code 会把对话历史存在~/.claude下tmux 保的是进程工具自己的状态文件保的是上下文。两者要配合好——如果工具进程被 kill 了但状态文件还在重启后可能能恢复如果状态文件被清了那 tmux 里进程还在也接不上之前的对话。我的做法是在 openrig 配置里给每个工具指定独立的状态目录并且把这个目录纳入备份范围。这样即使换机器把状态目录和 YAML 一起带过去工作台就能完整复现。4.3 多项目并行时的命名与切换同时跑三四个项目是常态tmux session 命名就要有规矩。我一般用项目名-用途的格式比如myapp-dev、myapp-test、otherproj-dev。切换时tmux ls列出所有 sessiontmux attach -t 名字进去。如果 session 太多记不住可以在 openrig 里加一个简单的列表命令读 YAML 里配置的 workspace 列表打印出每个 workspace 对应的 session 名和状态。这比手动tmux ls再对照记忆要靠谱。还有一个实用技巧给常用的 session 绑定快捷键。在 tmux 配置里加几行 bind比如bind C-m switch-client -t myapp按 Ctrl-b 再按 Ctrl-m 直接跳到 myapp。项目多了之后这个能省不少事。提示tmux 的 session 在服务器重启后会丢失。如果你的开发机不常重启问题不大如果经常重启可以考虑用插件做 session 持久化或者干脆把 openrig 的引导流程做成开机自启的一部分。5. 本地代理与端点切换让工具无感换后端5.1 代理到底在转什么本地代理的核心工作是协议转换和端点路由。Claude Code 发出的请求是 Anthropic 消息格式Codex 发出的是 OpenAI responses 格式而你的本地模型服务可能只认 OpenAI chat completions 格式。代理要做的是接收请求、识别来源和路径、把请求体转换成目标后端能懂的格式、转发、再把响应转换回工具期望的格式。举个具体的例子。Claude Code 请求/v1/messages请求体里有个messages数组每条消息有role和content。OpenAI chat completions 的请求体结构类似但字段名和嵌套方式有差异比如 system 消息的位置、工具调用的表达方式。代理需要把这些差异抹平。Codex 那边更麻烦一点因为它默认走/v1/responses这个接口的请求和响应格式跟 chat completions 又不一样。热词里那个 “failed while handling codex endpoint /responses” 的报错通常就是代理没有实现/responses路径的处理或者后端不支持这个接口导致转发失败。5.2 端点切换的配置化做法openrig 里我把端点切换做成配置驱动。YAML 里每个工具一段endpoint代理根据这个配置决定往哪转发。想从本地模型切到云端改一行配置、重启代理就行工具本身不用动。这里有个设计选择代理是按工具分端口还是共用一个端口按路径区分我倾向后者。一个代理进程监听一个端口根据请求路径前缀决定转发目标。比如/claude/*转发到 Claude 的后端/codex/*转发到 Codex 的后端。这样只需要维护一个代理进程日志也集中。但共用端口有个前提路径不能冲突。Claude Code 和 Codex 的默认路径分别是/v1/messages和/v1/responses本身不冲突所以直接按路径路由是可行的。如果两个工具用了相同路径那就得靠请求头里的特征来区分复杂度会上升。5.3 代理失败的典型排查路径代理出问题排查顺序我总结成三步。第一步确认代理进程活着且端口在听。curl http://127.0.0.1:8080/health之类的健康检查接口或者直接lsof -i :8080看端口占用。进程挂了或者端口没起来后面都不用查。第二步确认请求到达了代理。看代理日志里有没有对应的请求记录。如果工具报连接失败但代理日志里什么都没有说明请求根本没发到代理问题在工具的端点配置上。第三步确认转发目标可达且格式匹配。代理收到请求后转发给后端如果后端返回 4xx/5xx代理日志里应该有记录。这时候要看是后端不认这个路径、不认这个请求格式还是认证问题。把代理日志和后端日志对照着看基本能定位。我整理了一个速查表覆盖最常见的几类代理报错报错现象可能原因排查动作连接被拒绝代理未启动或端口错检查进程和端口监听404 路径不存在代理未实现该路径路由核对 routes 配置与请求路径400 请求格式错误协议转换不完整对比转换前后请求体401 认证失败密钥变量未注入或后端校验检查环境变量与后端配置响应解析失败响应格式转换有误抓取原始响应对比期望格式5.4 把代理日志接进 tmux代理日志我单独放一个 tmux window用tail -f实时看。这样工具那边一报错切到 proxy window 就能看到对应的请求和响应不用去翻文件。日志格式建议结构化一点至少包含时间戳、来源工具、请求路径、转发目标、响应状态码。出问题时按时间戳对齐工具日志和代理日志能快速定位是哪一步断的。如果日志量太大可以加个简单的过滤只打印非 2xx 的响应减少噪音。6. 常见问题与排查技巧实录6.1 工具启动就报认证失败这是最高频的问题。表现是 Claude Code 或 Codex 一启动就提示认证相关错误但你明明配了密钥。原因通常有三个密钥变量名写错了、变量没被注入到工具进程、或者 YAML 里把密钥写成了null。排查时先确认变量名。Claude Code 要的是ANTHROPIC_API_KEYCodex 要的是OPENAI_API_KEY大小写和拼写都不能错。然后确认注入方式——如果你是在 YAML 里写env段要确保引导流程真的把这些变量 export 到了启动工具的 shell 里。最后检查 YAML 值空字符串和null都会导致变量存在但值为空工具可能因此判定认证失败。注意本地模型服务通常不校验密钥内容但工具会检查变量是否存在且非空。填一个占位字符串即可比如local-no-auth。6.2 端点能连通但请求超时端点curl能通但工具发请求就超时。这种情况多半是请求格式不对导致后端处理卡住或者代理转发时没设超时。先看代理日志确认请求是否转发到了后端。如果转发了但后端迟迟不响应可能是后端在等一个它永远等不到的字段或者请求体太大触发了后端的某种限制。这时候把代理收到的原始请求体和转换后的请求体都打出来对比往往能发现问题。另一个可能是代理本身的超时设置太短。AI 编码请求的响应时间可能很长尤其是大模型生成长代码时。代理的读超时建议设到几分钟级别别用默认的几秒。6.3 tmux 里工具输出乱码或显示异常tmux 里跑 AI 编码工具偶尔会遇到输出乱码、颜色不对、光标位置错乱。这通常是终端类型和编码设置的问题。先确认TERM环境变量。在 tmux 里echo $TERM正常应该是screen-256color或tmux-256color。如果是dumb或者空颜色和光标控制就会出问题。可以在 tmux 配置里显式设置set -g default-terminal tmux-256color。编码方面确保LANG和LC_ALL设成了 UTF-8比如en_US.UTF-8。中文注释或输出乱码基本都是编码没设对。6.4 配置改了但工具行为没变改了 YAML 配置重启工具后行为还是老样子。这种情况先怀疑配置没被重新加载。有些引导流程只在启动时读一次配置改了文件但没重新跑引导工具用的还是旧环境变量。解决办法是让引导流程支持热重载或者至少在改配置后显式重新执行一遍。另外要确认工具本身有没有缓存配置——有些工具会把配置读进内存后就不再读文件必须完全重启进程才生效。还有一个隐蔽原因环境变量优先级。如果系统里已经有一个同名的环境变量YAML 里配的可能会被覆盖或者反过来。排查时在工具启动的 shell 里env | grep一下相关变量看实际生效的值是什么。6.5 多工具共用代理时的路径冲突Claude Code 和 Codex 共用一个代理端口时如果两者的请求路径有重叠就会路由错。虽然默认路径不冲突但如果你自定义过路径或者后端要求统一路径就可能撞上。解决办法是在代理路由配置里用更精确的匹配规则比如按请求头里的 User-Agent 或自定义标识来区分来源。或者干脆给每个工具分配独立端口牺牲一点简洁性换确定性。我一般优先选独立端口因为排查时更直观——看端口就知道是哪个工具的请求。6.6 会话恢复后上下文丢失tmux 会话恢复了工具进程还在但对话上下文没了。这通常是工具自己的状态文件出了问题而不是 tmux 的问题。检查工具的状态目录是否存在、是否有写权限、是否被意外清空。Claude Code 和 Codex 都会把会话历史存在用户目录下的隐藏文件夹里如果这些文件夹被清理工具扫掉了上下文自然就没了。把状态目录加入备份和排除清理的名单能避免这个问题。7. 我在实际使用中沉淀的几条经验配 openrig 这套东西最深的体会是配置的复杂度要控制在你能一眼看懂的范围。我一开始追求大而全YAML 写了上百行结果出问题时自己都记不清哪个字段控制哪个行为。后来砍到只保留真正会变的项——端点、模型、工作目录、会话名——其余全部用合理默认值维护成本一下子降下来了。另一个体会是日志要集中、要结构化。工具日志、代理日志、tmux 会话日志分散在三处时排查一个问题要在三个终端之间来回切。把它们汇总到一个地方按时间戳排序问题的因果链就清晰了。这个投入在项目多起来之后回报非常明显。最后分享一个小技巧给 openrig 的引导流程加一个doctor子命令一键检查所有依赖、端口、配置项、状态目录。每次换机器或者感觉环境不对劲时跑一下比手动一项项查快得多。这个命令不用很智能能把“什么没装、什么没起、什么配错了”列出来就够用了。