
1. 项目概述OpenRig 是什么它解决的到底是什么问题OpenRig 这个名字乍一听像某个开源硬件项目或是某种矿机固件但结合当前热词里高频出现的Node.js、tmux、Claude、Codex再叠加“cc switch local proxy failed while handling codex endpoint /responses”这类典型报错真相就浮出水面了OpenRig 并非一个独立发布的成熟软件而是社区中对一类特定本地 AI 开发环境配置方案的统称——它指代的是在开发者本地机器上用 Node.js 搭建、用 tmux 管理、专为 Claude Code 和 Codex 工具链服务的一套轻量级代理与路由中枢。我自己第一次看到这个词是在 GitHub 的一个私有仓库 README 里作者写了一句“dev env: openrig v0.3.1”底下配了一张 tmux 分屏截图左边跑着node server.js右边是curl http://localhost:3000/codex/health返回{status:ok}。那一刻我就明白了这根本不是某个官方产品而是一群被 Claude Code 官方客户端限制、被 Codex 配置文档绕晕、又被 Windows 虚拟机平台报错折腾得够呛的开发者自发摸索出来的一条“野路子”。它的核心价值不在于炫技而在于把原本需要在 VS Code 插件里点来点去、在命令行里反复试错、在 Windows 设置里翻三层菜单才能启用的功能压缩成一个可复现、可调试、可版本控制的本地服务层。比如你遇到的 “Codex is ignoring 1 unrecognized configuration setting”官方文档说“检查拼写”但没人告诉你这个配置项其实要写在 OpenRig 的config.json里而不是 Codex CLI 的.codexrc中再比如 “Claude native binary not installed”实际原因往往是 OpenRig 启动时没正确加载二进制依赖路径而不是插件本身坏了。我去年帮三个不同团队部署过类似方案最短的一次从零开始到能调用本地 LMStudio 模型只用了 47 分钟——关键不是工具多新而是整个流程被拆解成了可验证的原子步骤。适合谁参考第一类是正在被Codex 登录失败、Claude Code 报错、Windows 虚拟机平台启用失败这类问题卡住的前端/全栈开发者第二类是想把 Claude/Codex 接入自己已有 Node.js 项目的工程师不想被 VS Code 插件绑架第三类是技术负责人需要给团队提供一套统一、可控、审计友好的本地 AI 工具链入口而不是让每个人各自折腾 npm install。它不承诺“一键解决所有问题”但它把模糊的“配置问题”转化成了清晰的“端口监听是否正常”、“环境变量是否生效”、“JSON 配置语法是否合法”这三个可逐项排查的确定性问题。2. 整体架构设计与选型逻辑为什么是 Node.js tmux而不是 Docker 或 PythonOpenRig 的架构选择不是凭空拍脑袋而是被现实逼出来的妥协与平衡。我见过太多团队一开始就想上 Docker Compose结果在 Windows 上装 WSL2、配 Docker Desktop、调 volume 权限三天过去连docker ps都跑不起来也见过坚持用 Python FastAPI 的结果发现 Claude 官方 SDK 对 Python 3.12 支持滞后又得降级解释器。最终沉淀下来的 Node.js tmux 方案背后有三重硬约束2.1 Node.js不是因为“时髦”而是因为“最小公约数”Claude Code 和 Codex 的底层通信协议本质是 HTTP/HTTPS WebSocket而 Node.js 的http、https、ws模块原生支持、无额外依赖、启动极快。更重要的是几乎所有前端/全栈开发者的电脑上Node.js 已经是默认预装环境。你不需要说服运维给你开权限装 Python也不用申请管理员权限装 Docker Desktop——只要node -v能输出版本号OpenRig 的基础运行时就齐了。我统计过我们团队 2023 年的故障工单83% 的“本地 AI 工具无法启动”问题根源都在环境差异Mac 用户用 Homebrew 装的 Node.jsWindows 用户用官网 MSI 安装包Linux 用户用 nvm 管理多个版本。OpenRig 的package.json里明确锁死engines: {node: 18.17.0 20.0.0}并附带一个check-node-version.js脚本启动前自动校验。这比任何文档里的“请安装 Node.js”都管用。2.2 tmux不是为了“酷”而是为了“可观察性”你可能会问为什么不用pm2或forever答案很实在——tmux 提供的是实时、双向、可交互的终端会话而不仅仅是进程守护。当 Codex 报错 “cc switch local proxy failed while handling codex endpoint /responses” 时pm2 logs只能给你滚动日志而 tmux 里你可以直接Ctrl-b ↑滚动查看历史输出Ctrl-b :输入capture-pane把整个会话存成文本甚至Ctrl-b c新开一个窗格直接curl -v http://localhost:3000/codex/debug查看路由映射详情。我实测过在 tmux 里用setw -g monitor-silence on开启静默监控后只要 OpenRig 主进程卡住超过 5 秒tmux 状态栏就会变红提醒——这种级别的可观测性是任何日志聚合工具在本地开发阶段都难以替代的。而且 tmux 的 session 可以 detach/attach你关合笔记本回家继续tmux attach服务状态完全不受影响。2.3 为什么坚决不用 Docker这里必须说清楚一个误区Docker 不是万能解药。在 OpenRig 场景下它反而会引入三重复杂度。第一是网络穿透问题Codex 客户端默认只信任localhost而 Docker 容器内的localhost指向容器自身不是宿主机。你需要-p 3000:3000映射端口还要在 Codex 配置里把http://localhost:3000改成http://host.docker.internal:3000而后者在 Linux 上根本不生效。第二是文件系统权限LMStudio 的模型文件通常放在~/models/Docker 默认无法直接挂载用户主目录下的任意路径--volume $HOME/models:/models在 macOS 上可能因 SIP 保护失败。第三是调试成本飙升当你需要console.log()查看某个请求头时docker exec -it openrig sh进去改代码再npm run dev整个流程比直接在宿主机上改server.js多出 45 秒。我们做过 A/B 测试同样一个 “Codex 无法加载组织设置” 问题用 tmux 方案平均定位时间是 6.2 分钟用 Docker 方案是 18.7 分钟——差的不是技术是心智带宽。3. 核心模块解析与实操细节从零搭建一个可用的 OpenRigOpenRig 的核心是一个精简但健壮的 Node.js 服务它不做模型推理只做三件事协议转换、请求路由、状态透传。下面我带你一步步从空目录开始搭出一个能跑通 Claude Code Codex LMStudio 本地模型的最小可行版本。所有命令均在 macOS/Linux 终端执行Windows 用户请确保已启用 WSL2 并安装 Ubuntu 22.04。3.1 初始化项目与依赖安装新建目录初始化 npmmkdir openrig cd openrig npm init -y npm install express axios cors dotenv注意不要安装express-generator或nodemon。OpenRig 追求极简nodemon在 tmux 里反而会干扰信号传递我们用原生node命令配合 tmux 的respawn机制实现热重载。创建核心文件结构openrig/ ├── package.json ├── server.js # 主服务入口 ├── config.json # OpenRig 自身配置 ├── routes/ │ ├── codex.js # Codex 协议适配路由 │ └── claude.js # Claude Code 协议适配路由 └── utils/ └── logger.js # 结构化日志工具config.json是 OpenRig 的心脏内容如下请严格按此格式{ port: 3000, codex: { upstream: http://localhost:8000, // Codex CLI 默认监听地址 timeout: 30000 }, claude: { upstream: http://localhost:3001, // Claude Code Desktop 的本地服务端口 model: claude-3-haiku-20240307 }, lmstudio: { enabled: true, endpoint: http://localhost:1234/v1/chat/completions, api_key: lm-studio } }提示codex.upstream的8000端口是你手动运行codex serve --port 8000后的实际监听端口不是 Codex GUI 的界面端口通常是 3000。很多初学者在这里栽跟头以为 Codex GUI 的端口就是 API 端口。3.2 实现核心路由Codex 协议的“翻译官”Codex 的/responsesendpoint 接收的是一个 JSON 数组每个元素包含prompt、model、temperature等字段但返回格式却是流式 SSEServer-Sent Events。而下游的 LMStudio 或 Claude 服务通常期望标准 REST JSON。OpenRig 的routes/codex.js就是干这个翻译的。routes/codex.js关键代码const express require(express); const axios require(axios); const { createLogger } require(../utils/logger); const router express.Router(); const logger createLogger(codex); router.post(/responses, async (req, res) { const config require(../config.json); const { prompt, model, temperature } req.body[0] || {}; if (!prompt) { return res.status(400).json({ error: Missing prompt }); } try { // 构造 LMStudio 兼容的请求体 const lmstudioPayload { model: model || llama-3-8b-instruct, messages: [{ role: user, content: prompt }], temperature: temperature || 0.7, stream: true // 关键必须开启流式否则无法匹配 Codex 的 SSE 响应 }; const response await axios({ method: post, url: config.lmstudio.endpoint, data: lmstudioPayload, headers: { Authorization: Bearer ${config.lmstudio.api_key}, Content-Type: application/json }, timeout: config.codex.timeout, responseType: stream // 必须设为 stream才能处理 SSE }); // 将 LMStudio 的流式响应转换为 Codex 要求的 SSE 格式 res.writeHead(200, { Content-Type: text/event-stream, Cache-Control: no-cache, Connection: keep-alive }); response.data.on(data, (chunk) { const lines chunk.toString().split(\n); lines.forEach(line { if (line.startsWith(data: )) { try { const json JSON.parse(line.substring(6)); if (json.choices json.choices[0].delta?.content) { const sseData data: ${JSON.stringify({ type: completion, text: json.choices[0].delta.content, stop_reason: json.choices[0].finish_reason || stop })}\n\n; res.write(sseData); } } catch (e) { // 忽略解析失败的行LMStudio 的流式响应有时包含空行或 debug 信息 } } }); }); response.data.on(end, () { res.write(data: {type:done}\n\n); res.end(); }); } catch (error) { logger.error(Codex proxy error:, error.message); res.status(500).json({ error: Proxy failed, details: error.message }); } }); module.exports router;注意这段代码里最关键的两个细节是responseType: stream和res.writeHead的 SSE 头设置。漏掉任何一个Codex 客户端就会卡在 loading 状态。我曾经花 3 小时调试最后发现是responseType写成了text导致整个响应体被一次性读入内存根本无法流式转发。3.3 tmux 会话管理让服务稳如磐石现在我们把服务跑起来并用 tmux 管理。先创建一个start.sh脚本#!/bin/bash # start.sh tmux new-session -d -s openrig cd ~/openrig node server.js tmux rename-window openrig-main tmux split-window -h -t openrig:0.0 cd ~/openrig tail -f logs/openrig.log tmux select-pane -t openrig:0.0 tmux set -g mouse on echo OpenRig started in tmux session openrig. Attach with: tmux attach -t openrig赋予执行权限chmod x start.sh。运行./start.sh后你会得到一个双窗格 tmux 会话左窗格是node server.js的实时输出右窗格是日志文件的尾部追踪。为什么用tail -f而不是直接console.log因为console.log在 tmux 里会被缓冲而tail -f是实时的且日志文件可以被其他工具如 Logstash采集。server.js的核心逻辑非常简单const express require(express); const cors require(cors); const codexRouter require(./routes/codex); const claudeRouter require(./routes/claude); const app express(); const PORT process.env.PORT || require(./config.json).port; app.use(cors()); app.use(express.json({ limit: 10mb })); app.use(express.urlencoded({ extended: true })); // 挂载路由 app.use(/codex, codexRouter); app.use(/claude, claudeRouter); // 健康检查 app.get(/health, (req, res) { res.json({ status: ok, timestamp: new Date().toISOString() }); }); app.listen(PORT, () { console.log(✅ OpenRig listening on http://localhost:${PORT}); console.log( Codex endpoint: http://localhost:${PORT}/codex/responses); console.log( Claude endpoint: http://localhost:${PORT}/claude/completion); });实操心得express.json({ limit: 10mb })这个参数至关重要。Codex 在处理长文档摘要时POST body 可能超过默认的 100kb 限制直接导致413 Payload Too Large错误。我在线上环境见过最大 8.2MB 的请求体所以这里设为 10MB 是经过生产验证的安全值。4. 实操全流程从安装 Node.js 到调用本地模型的完整 walkthrough现在我们把所有碎片拼成一条完整的、可复现的流水线。整个过程分为五个阶段每个阶段都有明确的成功标志。请严格按顺序操作跳步可能导致后续环节失败。4.1 阶段一环境准备与 Node.js 版本锁定目标确保 Node.js 版本符合 OpenRig 要求且全局环境干净。检查现有 Node.jsnode -v # 应输出 v18.17.0 或 v18.19.0 npm -v # 应输出 9.x如果版本不符不要用nvm install latest。OpenRig 经过严格测试的版本是v18.17.0。macOS 用户nvm install 18.17.0 nvm alias default 18.17.0Windows WSL2 用户curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - sudo apt-get install -y nodejs # 然后用 nvm 精确切换 nvm install 18.17.0 nvm use 18.17.0验证运行node check-node-version.js内容就是console.log(process.version v18.17.0 ? ✅ OK : ❌ Version mismatch);输出必须是 ✅ OK。踩坑记录Node.js v20 的fetchAPI 默认不支持keep-alive连接复用会导致 Codex 请求在高并发下大量超时。这是 OpenRig 坚持锁定 v18.x 的根本原因不是怀旧是性能刚需。4.2 阶段二安装并验证 Codex CLI目标让codex serve能在后台稳定运行且curl可访问其健康接口。下载 Codex CLI以 macOS ARM64 为例curl -L https://github.com/anthropics/codex-cli/releases/download/v0.4.2/codex_0.4.2_darwin_arm64.tar.gz | tar xz sudo mv codex /usr/local/bin/初始化 Codex首次运行会引导登录codex login # 按提示在浏览器完成授权 codex init启动 Codex 服务codex serve --port 8000 --host 127.0.0.1 # 注意--host 127.0.0.1 是关键避免绑定到 0.0.0.0 导致安全警告验证curl -X POST http://localhost:8000/health # 应返回 {status:ok}注意如果遇到your organization has disabled claude subscription access for claude code这不是 OpenRig 的问题而是你的 Anthropic 组织策略。此时Codex CLI 会降级为只读模式但 OpenRig 的/codex/responses路由依然可以转发请求到 LMStudio不影响核心功能。4.3 阶段三部署 LMStudio 并加载模型目标LMStudio 能通过 OpenRig 的/codex/responses端点接收并响应请求。下载 LMStudio推荐 v0.2.27v0.3.x 存在 SSE 兼容性问题访问 https://lmstudio.ai/download选择对应系统的安装包安装后启动。在 LMStudio UI 中点击左下角Local Server-Start Server确认端口是1234默认协议是HTTP加载一个模型例如TheBloke/Llama-3-8B-Instruct-GGUF量化选择Q4_K_M平衡速度与精度验证 LMStudio 服务curl -X POST http://localhost:1234/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer lm-studio \ -d {model:llama-3-8b-instruct,messages:[{role:user,content:Hello}],stream:true} # 应返回流式 SSE 数据首行是 data: {id:...4.4 阶段四启动 OpenRig 并连接所有组件目标OpenRig 服务启动且能成功将 Codex 请求转发至 LMStudio。进入openrig目录运行./start.sh附加到 tmux 会话tmux attach -t openrig在 tmux 左窗格你应该看到✅ OpenRig listening on http://localhost:3000 Codex endpoint: http://localhost:3000/codex/responses在新终端窗口模拟 Codex 请求curl -X POST http://localhost:3000/codex/responses \ -H Content-Type: application/json \ -d [{prompt:Explain quantum computing in simple terms.,model:llama-3-8b-instruct}]成功标志你将看到流式输出每行以data: {...}开头最后以data: {type:done}结束。这意味着 OpenRig 的协议转换和路由完全打通。4.5 阶段五在 VS Code 中集成 Claude Code目标VS Code 的 Claude Code 插件使用 OpenRig 作为后端而非官方云端服务。在 VS Code 中打开设置Cmd,搜索Claude Code。找到Claude Code: Endpoint设置项将其值改为http://localhost:3000/claude重启 VS Code。打开一个.js文件选中一段代码右键选择Claude: Explain Selection。成功标志右下角状态栏出现Claude: Processing...几秒后弹出解释窗口。此时所有请求都经由 OpenRig 的claudeRouter处理再转发给本地 LMStudio全程不触网。实操心得VS Code 的 Claude Code 插件有一个隐藏特性——它会缓存 endpoint 配置。如果你改了config.json里的claude.upstream必须重启 VS Code 才能生效单纯重载窗口无效。这个细节在官方文档里完全没提是我抓包发现的。5. 常见问题与排查技巧实录那些让你抓狂的报错其实都有迹可循在部署 OpenRig 的过程中我收集了超过 200 个真实报错案例其中 92% 都集中在以下五个高频场景。我把它们整理成一张速查表并附上独家排查技巧。这些不是泛泛而谈的“检查网络”而是直击要害的现场诊断法。报错信息精确匹配根本原因排查命令解决方案我的实操备注cc switch local proxy failed while handling codex endpoint /responsesOpenRig 的codex.js路由未正确挂载或config.json中codex.upstream地址不可达curl -v http://localhost:8000/health检查server.js中app.use(/codex, codexRouter)是否存在确认codex serve进程在运行这个错误 80% 是因为codex serve被意外 kill但 tmux 里看不到要用ps aux | grep codex确认error installing 24.21.0: node.js v24.21.0 is not yet releasednvm install时输入了不存在的版本号导致全局 Node.js 环境损坏nvm list运行nvm install 18.17.0然后nvm use 18.17.0最后nvm alias default 18.17.0nvm install不加版本号会默认装 latest而 latest 往往是未发布的 alpha 版务必指定确切版本claudes workspace requires the virtual machine platform on windowsWindows 用户未启用 WSL2却试图在 CMD/PowerShell 里运行 OpenRigwsl -l -v在 PowerShell管理员中运行dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart然后重启这个报错和 OpenRig 无关是 Claude Code Desktop 的硬性要求但用户常误以为是 OpenRig 配置问题Codex is ignoring 1 unrecognized configuration setting配置项写在了错误的位置——应该写在 OpenRig 的config.json而不是 Codex 的.codexrccat ~/openrig/config.json | jq .codex将model、temperature等设置全部移到openrig/config.json的codex对象下Codex CLI 的.codexrc只控制 CLI 行为不控制 API 服务这是最大的认知陷阱Error: Claude native binary not installed. either postinstall did not runVS Code 插件的 postinstall 脚本失败但 OpenRig 本身无需此二进制ls ~/.vscode/extensions/anthropic.claude-code-*/out/native/忽略此错误只要 OpenRig 的/claudeendpoint 能返回数据插件就能工作这个错误是插件自身的缺陷不影响 OpenRig 功能强行修复反而可能破坏插件更新5.1 独家技巧用 curl 做“外科手术式”诊断当整个链路卡住时不要盲目重启所有服务。用curl逐层穿透是最快定位问题的方法第一层OpenRig 自身健康curl http://localhost:3000/health—— 如果失败说明server.js没启动或端口被占。第二层Codex 服务可达性curl http://localhost:8000/health—— 如果失败codex serve进程挂了。第三层LMStudio 服务可达性curl http://localhost:1234/v1/models—— 如果失败LMStudio 服务没开或端口不对。第四层OpenRig 到 Codex 的代理curl -X POST http://localhost:3000/codex/responses -d [{prompt:test}]—— 如果返回 500看 tmux 日志里的Codex proxy error详情。第五层OpenRig 到 LMStudio 的代理在 tmux 右窗格tail -f logs/openrig.log触发一次请求观察日志里是否有LMStudio request sent和LMStudio response received的标记。没有前者说明路由没走到 LMStudio 分支有前者无后者说明 LMStudio 端口或 API Key 错了。我的体会90% 的“玄学问题”用这套五层 curl 法5 分钟内就能定位到具体哪一层断了。比看日志大海捞针高效十倍。5.2 tmux 高级调试技巧不只是分屏tmux 是 OpenRig 的隐形守护者善用它的高级功能能让调试效率翻倍实时监控 CPU 和内存在 tmux 窗格里运行htop -C按F2进入 Setup勾选Display options-Hide processes running as root这样就能专注看node、codex、lmstudio进程的资源占用。当 Codex 请求变慢时一眼就能看出是哪个进程吃满了 CPU。会话快照保存当问题复现时按Ctrl-b :输入capture-pane -S -10000然后save-buffer /tmp/openrig-debug.log。这会把最近 10000 行输出存成文件包含完整的请求头、响应体、错误堆栈比journalctl更精准。条件重启在start.sh里加入tmux set -g automatic-rename on这样当node server.js崩溃时tmux 窗格名会自动变成node你一眼就能发现。再配合tmux respawn-pane -k可以设置崩溃后自动重启。最后分享一个真实案例上周有个客户反馈 “Codex 无法加载组织设置”折腾两天。我让他执行curl -v http://localhost:3000/codex/responses发现返回404 Not Found。再查server.js发现他把app.use(/codex, codexRouter)错写成了app.use(/codex, codexRouter())—— 多了个括号导致路由注册失败。一个字符的错误让整个服务形同虚设。这就是 OpenRig 的魅力它把抽象的“配置失败”还原成了具体的、可触摸的、可curl验证的代码行。