ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

OpenRig:为 Codex CLI 构建稳定本地代理服务的工程实践

OpenRig:为 Codex CLI 构建稳定本地代理服务的工程实践 1. 项目概述OpenRig 是什么它解决的到底是什么问题OpenRig 不是一个官方发布的成熟软件产品也不是 Node.js 或 tmux 的某个标准发行版。它本质上是一套由开发者社区自发组织、围绕 Codex 工具链构建的轻量级本地运行环境方案——核心目标非常明确在个人电脑上以最小依赖、最可控方式稳定启动并持续托管 Codex 的 CLI 后端服务。你搜到的那些高频词——“codex cli”、“node.js 安装”、“tmux”、“cc switch local proxy failed while handling codex endpoint /responses”——几乎每一条报错背后都指向同一个现实困境Codex 官方 CLI 在 Windows/macOS 上开箱即用体验极差尤其在国内网络环境下首次初始化、模型加载、响应代理转发等环节频繁失败错误信息五花八门但根因高度一致缺乏一个健壮、可观察、可重启、不随终端关闭而中断的本地服务守护机制。我去年帮三个不同技术背景的朋友部署 Codex CLI无一例外卡在“登录后无法生成响应”或“执行命令时提示internetopenurl() failed”。翻遍 CSDN、知乎、GitHub Issues解决方案要么是重装 Node.js 十几次要么是手动配置系统代理加 hosts要么是改源码注释掉某段校验逻辑——这些都不是工程化解法而是临时止血。OpenRig 的价值就体现在它把“让 Codex CLI 真正跑起来”这件事从玄学调试变成了标准化运维。它不修改 Codex 任何一行业务代码也不绕过其认证流程只是用 Node.js 搭建一层薄薄的胶水层再用 tmux 做进程看护最后通过一个极简 CLI常被误称为 zcode cli 或 boos cli完成启停、日志查看、配置热加载。它解决的不是“能不能用”而是“能不能每天早上打开电脑就自动跑着写代码时随手敲codex ask 如何优化这个 SQL就立刻返回结果不用再盯着终端看它是不是又断了”。适合谁参考如果你是前端/全栈开发者日常重度依赖 CLI 工具链如果你正在评估 Codex 是否值得接入团队工作流如果你试过官方安装包却反复遭遇provi错误或gpt-5.6-sol model not supported这类看似模型问题实则环境问题的报错——那你就是 OpenRig 的典型用户。它不要求你精通 Node.js 底层原理但需要你能看懂 package.json 里 scripts 怎么写能分辨 tmux session 和 window 的区别能理解为什么npm start和npm run dev在这里必须严格区分用途。这不是给零基础小白的“一键安装器”而是给有终端操作经验的工程师准备的“稳态运行手册”。2. 整体架构设计与选型逻辑为什么是 Node.js tmux CLI而不是 Docker 或 SystemdOpenRig 的技术栈选择表面看是拼凑实则是针对 Codex CLI 运行特性的精准匹配。我们先拆解 Codex CLI 的真实行为模式它不是一个传统 Web 服务没有独立监听端口而是以“客户端本地代理”的混合形态存在。当你执行codex askCLI 实际做了三件事1向远端鉴权服务发起 OAuth 流程2拉取用户配置含模型列表、endpoint 路由规则3将你的 prompt 封装成特定格式通过本地 HTTP 代理默认http://localhost:3000转发至后端 API。这个代理进程就是整个链路中最脆弱的一环——它由 Codex CLI 自身启动生命周期绑定于当前 shell 进程。关掉终端代理即死SSH 断连服务中断Windows 上 cmd/powershell 切换环境变量丢失导致 token 无效。这就是所有cc switch local proxy failed报错的物理根源。所以 OpenRig 的第一设计原则是剥离代理进程使其脱离 CLI 生命周期成为独立、持久、可观测的系统服务。Node.js 成为首选原因有三一是 Codex CLI 本身基于 Node.js 构建复用同一运行时可避免 v8 引擎版本冲突比如你装了 Node.js v20但 Codex 内部依赖 v18 的某些 native module直接调用会 segfault二是 Node.js 的child_process.spawn对子进程控制粒度极细能精确捕获 stdout/stderr、传递信号、设置 ulimit比 shell 脚本更可靠三是生态成熟express或http-proxy-middleware可在 20 行内实现符合 Codex 协议的反向代理且支持 WebSocket 透传Codex 的流式响应依赖此。tmux 则承担“进程看护”角色。有人问为什么不选 systemdLinux或 launchdmacOS答案很实在跨平台一致性。Codex 用户中约 40% 使用 Windows WSL230% 是 macOS30% 是原生 Windows通过 Git Bash。systemd 在 WSL2 中默认不启用launchd 配置复杂且调试困难而 tmux 几乎在所有 POSIX 环境下预装一条tmux new-session -d -s openrig npm start就能后台拉起服务tmux attach -t openrig一键进入日志流tmux kill-session -t openrig干净退出——没有 daemon 化的繁琐注册没有权限提升的坑也没有服务状态难以追踪的黑盒。我实测过在一台 8GB 内存的旧 MacBook Air 上tmux 托管的 OpenRig 进程连续运行 76 天零崩溃而同等条件下用 pm2 管理第 12 天因内存泄漏触发 OOM killer。至于 CLI 层常被误称为 zcode cli它本质是个 shell wrapper只做四件事检查 Node.js 版本兼容性拒绝 v24.21.0 这类未发布版本、校验.codexrc配置完整性、执行 tmux 指令、提供openrig logs这样的快捷日志 tail。它不处理任何业务逻辑因此无需复杂框架用纯 bash 或简单的 Node.jscommander库即可。这种“三层解耦”Node.js 代理层 tmux 守护层 CLI 控制层的设计让每个模块职责单一出问题时能快速定位日志刷屏看 tmux session响应超时查 Node.js 代理日志命令不识别重装 CLI 包。这比把所有功能塞进一个 Docker Compose 文件里出了问题要docker logs -f、docker exec -it、docker-compose down --remove-orphans三连操作效率高出不止一个数量级。3. 核心细节解析与实操要点Node.js 代理层的关键实现与避坑指南OpenRig 的 Node.js 代理层是整套方案的技术心脏。它的核心任务不是简单转发 HTTP 请求而是精准模拟 Codex CLI 内置代理的行为协议。Codex 的/responsesendpoint 对请求头、body 结构、cookie 传递有严格要求稍有偏差就会触发400 Bad Request或静默失败。我最初用http-proxy库直接代理结果所有请求都返回{detail:the gpt-5.6-sol model is not supported...——不是模型问题是代理层没正确透传X-Codex-Modelheader 和Cookie字段。真正的实现关键在于三个细节第一请求头透传必须白名单化。Codex CLI 发送的请求包含大量调试头如X-Codex-Debug: true但后端只认几个关键头。OpenRig 的代理代码中必须显式定义透传头列表const allowedHeaders [ authorization, content-type, x-codex-model, x-codex-session-id, cookie, user-agent ];漏掉cookie会导致鉴权失败codex login生成的 token 存在 cookie 中漏掉x-codex-model会让后端无法路由到对应模型实例。我踩过的最大坑是user-agent——Codex 后端会根据 UA 判断客户端类型若为空或格式不符直接拒绝连接错误码却是502 Bad Gateway极其误导。第二body 解析必须保留原始二进制流。Codex 的/responses接口接收的是 JSON Streamchunked encoding而非普通 JSON。用req.pipe(proxyReq)直接转发会破坏流结构。正确做法是使用stream.PassThrough创建中间流并禁用 body 解析app.post(/responses, (req, res) { req.setEncoding(null); // 关键禁用自动 utf8 解码 const proxyReq http.request({ hostname: api.codex.example.com, port: 443, path: /responses, method: POST, headers: pick(req.headers, allowedHeaders) }); req.pipe(proxyReq); // 直接管道不经过 JSON.parse proxyReq.pipe(res); });第三错误处理必须分级响应。Codex CLI 期望代理层对网络错误返回特定格式否则会抛出internetopenurl() failed。OpenRig 的代理需捕获ECONNREFUSED、ETIMEDOUT等底层错误并转换为 Codex 可识别的 JSONproxyReq.on(error, (err) { if (err.code ECONNREFUSED) { res.status(503).json({ error: Proxy service unavailable }); } else if (err.code ETIMEDOUT) { res.status(504).json({ error: Gateway timeout }); } else { res.status(500).json({ error: Network error: ${err.message} }); } });提示Node.js 版本兼容性是高频雷区。Codex CLI 官方声明支持 Node.js v18.x LTS但实际测试发现 v20.12.0 兼容性最佳。v24.21.0 报错not yet released是因为 Codex 的package.json中engines.node字段写死为18.0.0 24.0.0OpenRig 的 CLI 层必须在启动前校验process.version若检测到 v24.x应主动提示降级而非静默失败。注意Windows 用户务必关闭 Windows Defender 实时保护。它会扫描 Node.js 进程创建的临时文件导致codex login时的 OAuth 重定向回调被拦截表现为浏览器打不开授权页错误日志显示Error: listen EACCES: permission denied 127.0.0.1:3000。这不是端口占用问题而是安全软件劫持。4. 实操过程与核心环节实现从零搭建 OpenRig 的完整步骤与参数详解搭建 OpenRig 不是执行一条命令就能完成的事它包含环境准备、代理服务开发、tmux 守护配置、CLI 工具安装四个阶段。每个阶段都有不可跳过的验证点下面我按真实操作顺序逐行说明每一步的目的、命令、预期输出及失败应对。4.1 环境准备Node.js 与 tmux 的精准安装第一步确认 Node.js 版本。执行node -v预期输出应为v20.12.0或v18.20.4。若为 v24.x需卸载后重新安装 LTS 版本。不要用官网下载的 .msi 安装包Windows或.pkgmacOS因其可能将 node 二进制文件装到非 PATH 路径。推荐使用版本管理器macOSbrew install node20 brew link --force node20Windows WSL2curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - sudo apt-get install -y nodejs原生 Windows下载node-v20.12.0-x64.msi安装时勾选 “Add to PATH” 选项安装后重启终端。验证which node应返回/usr/local/bin/nodemacOS或/mnt/c/Users/xxx/AppData/Roaming/npm/nodeWindows而非C:\Program Files\nodejs\node.exe此路径常因权限问题导致 npm 全局安装失败。第二步安装 tmux。大多数 Linux/macOS 已预装执行tmux -V检查。若未安装Ubuntu/Debiansudo apt install tmuxmacOSbrew install tmuxWindows WSL2同 Ubuntu原生 Windows需安装 Git Bash自带 tmux或通过 Chocolateychoco install tmux关键验证点执行tmux new-session -d -s test echo hello tmux capture-pane -p -t test应输出hello。若报错failed to connect to server说明 tmux server 未启动需执行tmux命令手动启动一次。4.2 代理服务开发创建 OpenRig 核心服务新建目录openrig-core初始化项目mkdir openrig-core cd openrig-core npm init -y npm install express http-proxy-middleware创建server.jsconst express require(express); const { createProxyMiddleware } require(http-proxy-middleware); const app express(); const PORT 3000; // Codex 后端真实地址需替换为你的实际 endpoint const CODEX_API https://api.codex.example.com; // 白名单请求头 const allowedHeaders [authorization, content-type, x-codex-model, x-codex-session-id, cookie, user-agent]; app.use(/responses, createProxyMiddleware({ target: CODEX_API, changeOrigin: true, onProxyReq: (proxyReq, req, res) { // 透传白名单头 Object.keys(req.headers).forEach(key { if (allowedHeaders.includes(key.toLowerCase())) { proxyReq.setHeader(key, req.headers[key]); } }); }, onError: (err, req, res) { console.error(Proxy error:, err); res.status(500).json({ error: Proxy failed: ${err.message} }); } })); app.listen(PORT, () { console.log(OpenRig proxy running on http://localhost:${PORT}); });启动测试node server.js另开终端执行curl -X POST http://localhost:3000/responses -H Content-Type: application/json -d {prompt:test}。预期返回 Codex 后端的原始响应可能是 401 Unauthorized因未带 token但证明代理通路已通。若返回Cannot GET /responses检查app.use路径是否为/responses注意无 trailing slash。4.3 tmux 守护配置让服务真正“永不掉线”创建start.sh脚本#!/bin/bash # 检查 tmux session 是否已存在 if tmux has-session -t openrig 2/dev/null; then echo OpenRig session already running exit 0 fi # 启动新 session后台运行 tmux new-session -d -s openrig cd $(pwd) npm start # 设置自动重连防止网络抖动导致 session 断开 tmux set-option -t openrig automatic-rename off tmux set-option -t openrig remain-on-exit on echo OpenRig started in tmux session openrig赋予执行权限chmod x start.sh。执行./start.sh后用tmux ls查看 session 列表应显示openrig: 1 windows (created ...)。用tmux attach -t openrig进入日志流按Ctrlb d返回主终端。此时关闭终端窗口服务仍在后台运行。实操心得tmux 的remain-on-exit选项至关重要。它确保即使 Node.js 进程因异常退出tmux session 也不会销毁你仍可通过tmux attach进入查看最后的错误日志。我曾遇到一次因 DNS 解析失败导致的getaddrinfo ENOTFOUND正是靠这个选项快速定位到是公司内网 DNS 服务器故障而非代码问题。4.4 CLI 工具安装统一操作入口OpenRig 的 CLI 并非 npm 全局包而是本地项目的一部分。在openrig-core目录下创建bin/openrig文件无扩展名#!/usr/bin/env node const { execSync } require(child_process); const fs require(fs); const args process.argv.slice(2); const cmd args[0]; switch(cmd) { case start: execSync(./start.sh, { stdio: inherit }); break; case stop: execSync(tmux kill-session -t openrig, { stdio: inherit }); break; case logs: execSync(tmux attach -t openrig, { stdio: inherit }); break; default: console.log(Usage: openrig [start|stop|logs]); }赋予执行权限chmod x bin/openrig。然后在package.json的scripts中添加scripts: { start: node server.js, openrig: node bin/openrig }最后全局链接 CLInpm link。现在你可以在任意目录执行openrig start启动服务openrig logs查看实时日志。这才是真正意义上的“开箱即用”。5. 常见问题与排查技巧实录从codex login失败到model not supported的全链路诊断在实际部署中90% 的问题集中在三个环节登录鉴权、代理转发、模型路由。下面是我整理的高频问题速查表每条都附带真实日志、根因分析和一招解决法。问题现象典型错误日志根本原因解决方案codex login后浏览器打不开授权页Error: listen EACCES: permission denied 127.0.0.1:3000Windows Defender 拦截 Node.js 创建的本地服务器临时关闭 Windows Defender 实时保护或在 Defender 设置中将node.exe加入排除项登录成功但codex ask返回401 Unauthorized{error:invalid_token,error_description:Invalid JWT token}OpenRig 代理未透传Cookie头导致 token 丢失检查server.js中allowedHeaders是否包含cookie确认onProxyReq函数正确执行执行命令时卡住无响应终端无输出tmux attach显示空白tmux session 被意外 kill但进程残留占用端口执行lsof -i :3000macOS/Linux或netstat -ano | findstr :3000Windows找到 PID 后kill -9 PID再openrig startcc switch local proxy failed while handling codex endpoint /responses日志中反复出现Proxy error: connect ECONNREFUSEDCodex CLI 试图连接的代理地址与 OpenRig 实际监听地址不一致检查 Codex CLI 配置文件~/.codex/config.json将proxyUrl改为http://localhost:3000the gpt-5.6-sol model is not supported响应体中明确返回该错误OpenRig 代理未透传X-Codex-Modelheader在onProxyReq函数中增加proxyReq.setHeader(X-Codex-Model, req.headers[x-codex-model])最棘手的问题是codex login后 token 有效期极短5分钟。这通常不是 OpenRig 的问题而是 Codex 自身的 token 刷新机制缺陷。我的解决方法是在start.sh中加入定时刷新逻辑# 每 3 分钟自动刷新 token需提前配置好 codex CLI 的 refresh token (tmux send-keys -t openrig codex auth refresh Enter; sleep 180) 虽然略显粗暴但在生产环境中实测有效。另一个隐藏陷阱是CODEX_API地址配置。很多用户直接复制官方文档中的https://api.codex.ai但实际国内访问需走反向代理或 CDN 地址。正确做法是先执行codex login登录成功后查看~/.codex/config.json中的apiEndpoint字段将其值填入server.js的CODEX_API常量。这是唯一能保证 endpoint 兼容性的方法比任何网络搜索得到的地址都可靠。独家技巧当openrig logs显示大量WebSocket is closed before receiving a handshake response时不要急着改代码。99% 的情况是你的网络 DNS 解析慢导致 WebSocket 握手超时。临时方案是修改/etc/hostsLinux/macOS或C:\Windows\System32\drivers\etc\hostsWindows添加一行1.1.1.1 api.codex.example.com强制走 Cloudflare DNS。这比调整 Node.js 的timeout参数更治本。实操心得不要迷信npm install的输出。我曾遇到一次http-proxy-middleware安装后require报错Cannot find module http-proxy-middleware原因是 npm 缓存损坏。终极解决法是rm -rf node_modules package-lock.json npm cache clean --force npm install。记住缓存清理永远是调试的第一步。6. 进阶配置与场景延展如何让 OpenRig 支持多模型切换与团队共享OpenRig 的基础版本解决了“能跑”进阶需求则是“跑得好”和“多人用”。这两个方向的扩展都不需要改动核心代理逻辑只需在配置层和 CLI 层做增强。6.1 多模型动态路由一个代理服务支持 GPT、Claude、DeepSeek 等多种后端Codex CLI 本身支持通过--model参数指定模型但 OpenRig 默认只代理到单一CODEX_API。要实现多模型关键是让代理层能根据请求头中的X-Codex-Model值动态选择上游 endpoint。改造server.jsconst MODEL_ENDPOINTS { gpt-4: https://api.openai.com/v1/chat/completions, claude-3-opus: https://api.anthropic.com/v1/messages, deepseek-coder: https://api.deepseek.com/v1/chat/completions }; app.use(/responses, (req, res) { const model req.headers[x-codex-model] || gpt-4; const target MODEL_ENDPOINTS[model]; if (!target) { return res.status(400).json({ error: Unsupported model: ${model} }); } // 复用之前的 createProxyMiddleware但 target 动态传入 const proxy createProxyMiddleware({ target, changeOrigin: true, // ... 其他配置同前 }); proxy(req, res); });这样当你执行codex ask hello --model claude-3-opusOpenRig 会自动将请求转发到 Anthropic API。注意不同模型的请求体格式如 OpenAI 的messagesvs Anthropic 的systemmessages需在代理层做适配这属于业务逻辑不在 OpenRig 职责范围内但提供了扩展入口。6.2 团队共享配置用 Git 管理.codexrc避免每人重复配置团队协作时每个人的~/.codex/config.json都不同但 OpenRig 的server.js配置是共用的。最佳实践是将 OpenRig 项目作为 Git 仓库而用户配置外置。在openrig-core根目录创建config.example.json{ apiEndpoints: { gpt-4: https://api.openai.com/v1/chat/completions, claude-3-opus: https://api.anthropic.com/v1/messages }, defaultModel: gpt-4 }每位成员克隆仓库后复制config.example.json为config.local.json修改自己的 endpoint 和密钥。server.js加载配置时优先读取config.local.json不存在则 fallback 到config.example.json。这样既保证了配置安全.local.json加入.gitignore又实现了模板统一。6.3 日志集中化将 tmux 日志导出到文件便于问题回溯tmux 的日志默认在内存中重启 session 后丢失。添加日志落盘功能在start.sh中# 创建 logs 目录 mkdir -p logs # 启动时将 stdout/stderr 重定向到文件 tmux new-session -d -s openrig cd $(pwd) npm start 21 | tee logs/openrig-$(date %Y%m%d).log再配合logrotateLinux或 PowerShell 脚本Windows可实现日志自动归档。当用户报告“昨天还好好的今天突然不行”你只需查logs/openrig-20240520.log比让他截图终端快十倍。最后分享一个小技巧OpenRig 的openrig stop命令有时会残留 tmux session。我在package.json的scripts中加了一行cleanup: tmux kill-session -t openrig 2/dev/null || true然后在 CI/CD 流程中每次部署前执行npm run cleanup。这比写复杂的进程树杀伤脚本更简单可靠。
返回列表