先说结论:SSH能正常连上远程服务器,只代表传输层没问题,不代表服务器上跑的应用链路就一定是通的。很多人在本地终端敲ssh user@host登录成功后,顺手再跑一遍 Codex 的登录或任务命令,结果直接迎面撞上 403 Forbidden,偶尔还会在报错里看到一行cc switch local proxy failed while handling codex endpoint /responses。这行提示乍看让人懵,但只要把 Codex 的认证、配置、网络出口这条链路一层层拆开看,403 的根因其实就集中在几个点上。
这篇文章我按实战排查的顺序来写,从 HTTP 状态码语义、Codex CLI 的登录机制、配置文件和环境变量,到用 curl 直连验证请求来源,再到专门解析cc switch local proxy failed这条报错,最后附上我自己的避坑清单和一张速查表。适合在远程服务器上做开发、用 Codex 写代码或者维护 AI 编程工具链的读者参考。
1. 先别急着重装:403到底被哪一层拒绝的
1.1 SSH能连上不等于Codex请求能通过
我遇到过不少朋友,第一反应是“SSH 都连得上,那服务器肯定没问题”。这个判断有个盲区:SSH 走的是 22 端口的加密通道,而 Codex CLI 联网走的是标准的 HTTPS 请求,这两条路在逻辑上是完全独立的。SSH 连接成功,验证的是远程服务器的 sshd 服务正常、密钥或密码认证通过了;而 Codex 发出 HTTP 请求后,能不能通过取决于服务端的鉴权、账号权限、请求头、出口 IP 等多个因素。
你可以把这个场景想成进一栋写字楼:SSH 登录像是用门禁卡进了大楼大门,但 403 是你走到某一层某间办公室门口,门禁系统明确拒绝你进入。大楼大门开了,不代表所有房间都对你开放。Codex 服务端返回 403,意思是“我收到了你的请求,也知道你在请求什么,但我拒绝执行”。这件事跟 SSH 通道本身基本无关,排查时一定要把思路从“SSH 不通”切换到“HTTP 请求链路出问题”。
另一个容易误导人的地方是,403 不像 502、504 那样是明显的网关故障,它看起来像“权限问题”,但实际原因可能五花八门。如果不先定位 403 是哪一层返回的,很容易陷入“删配置重装 Codex”的循环里,折腾半天问题还在。
1.2 两类最常见的403现象:登录时拒绝与请求时拒绝
按我自己排查的经验,Codex 在远程服务器上出现 403,基本可以分成两类场景,而且两类场景的排查方向差别很大。
第一类是登录环节报 403。在本地执行codex login,CLI 通常会拉起浏览器跳转到服务商的授权页面,授权完成后回调本地服务换取令牌。但在 SSH 远程环境里,终端没有图形化界面可以在本地拉起浏览器,登录流程会退化成设备码方式:终端给你一个 URL 和一段设备码,你在自己电脑浏览器里打开并输入确认。如果在这个环节报 403,问题往往出在令牌换取失败、设备码过期、或者账号本身没有 Codex 的访问权限。
第二类是任务执行环节报 403。登录看起来正常,但你在终端里敲codex让它执行代码任务时,提示 403 或cc switch local proxy failed while handling codex endpoint /responses。这类问题的重灾区是环境变量污染、base_url 被覆盖、代理配置拦截、token 过期没有及时刷新。下面几章我会按这两类场景交叉展开,把完整排查流程写出来。
2. Codex登录链路与配置文件拆解
2.1 Codex CLI认证流程与SSH环境下的登录差异
先说说 Codex CLI 的正常认证流程,理解这个流程之后,你就知道远程 SSH 环境里为什么容易出问题。
在本地环境执行codex login时,CLI 会启动一个临时的本地 HTTP 服务,同时调用系统默认浏览器打开服务商的授权页面。浏览器里授权完成后,服务商会把授权码通过回调地址(通常是http://127.0.0.1:某个端口/callback)传回给本地临时服务,CLI 拿到授权码后,再向后端令牌接口换取访问令牌,最后把令牌写入配置文件。整套流程依赖“本机浏览器 + 本机回调端口”这两件事。
SSH 连接远程服务器后,情况完全变了:远程终端里没有你的桌面浏览器,localhost 回调端口也只是在服务器本机监听,跟你本机浏览器之间隔着一层 SSH 会话。这时候 Codex CLI 一般会降级成设备码模式:在终端直接输出一个 URL 和设备码,让你在任意一台电脑的浏览器里打开 URL、输入设备码完成授权。设备码模式在纯终端环境里是能用的,但它对令牌接口的校验更严格,一旦设备码过期、输入错误,或者账号权限不够,令牌接口就会直接返回 403。
所以,如果你在远程服务器上执行codex login时遇到 403,我的建议是:先确认是不是设备码过期或输错,重新执行一次codex login,拿到新的设备码后尽快完成授权;如果重新走一遍流程仍然 403,那大概率是账号级别的问题,去服务商官网确认自己的账号是否开通了 Codex 功能。
2.2 配置文件和Token到底放在哪里
Codex CLI 的配置通常放在用户目录下的.codex文件夹里。我以常见的目录结构为例,具体文件名和字段可能随版本不同略有变化,但排查思路是一样的:
~/.codex/ ├── config.toml ├── auth.json └── logs/config.toml是主配置文件,包含模型选择、API 基础地址、语言偏好等。一个典型的配置长这样:
[model] name = "gpt-5-codex" [api] base_url = "https://api.openai.com/v1" [language] language = "zh-CN"auth.json里保存的是登录后拿到的令牌信息,一般是一个 JSON 对象,包含访问令牌和过期时间。比如:
{ "tokens": { "access_token": "eyJhbGciOi...", "refresh_token": "eyJhbGciOi..." }, "expires_at": "2025-01-15T10:30:00Z" }排查 403 时,我强烈建议先做两个操作。第一个操作是检查config.toml里的base_url字段,看看它是不是被改成了一些第三方兼容端点。网上确实有一些教程教人把base_url指到其他模型服务上,如果那个服务端不支持 Codex 定义的接口规范,或者要求额外认证头,你请求/responses端点时就会收到 403。第二个操作是检查auth.json的权限,在共享开发机上尤其重要。如果这个文件的权限是-rw-rw-rw-,其他用户可能读得到你的 token,而且某些安全策略会拒绝这种暴露权限下的请求。可以用这条命令修复:
chmod 600 ~/.codex/auth.json2.3 容易被忽略的环境变量覆盖问题
配置文件只是表面一层,真正坑人的往往是环境变量。Codex CLI 在启动时会读取一系列环境变量,这些变量的优先级通常比config.toml高。我在排查时就遇到过一次,明明config.toml里写的base_url是正确的,但env里早就有人export了一个旧地址,CLI 优先用了环境变量里的值,结果请求全部打到错误的目标上,返回 403。
建议第一步先看环境变量,在 SSH 会话里执行:
env | grep -i -E "codex|openai|api_base|proxy"重点看这几类变量:
| 环境变量 | 影响 |
|---|---|
CODEX_API_BASE_URL/OPENAI_API_BASE | 直接覆盖 API 基础地址,改错就会请求到不存在的端点 |
HTTP_PROXY/HTTPS_PROXY | 让 CLI 的请求强制走某个本地代理,代理异常或拦截时会出现 403 |
NO_PROXY | 指定哪些域名不走代理,如果没包含 Codex 的 API 域名,请求会被错误转发 |
尤其要注意 SSH 登录时的 shell 配置文件。你本地可能没设置代理,但远程服务器的.bashrc或.profile里被人加了一行export HTTPS_PROXY=http://127.0.0.1:7890,一登录会话就自动生效。Codex CLI 发请求时带着代理设置走,代理那边的网关对/responses端点的限流或拦截策略比较激进,就会返回 403。
我在处理这类问题时的习惯是:在干净的 SSH 会话里先执行env | grep -i proxy看一眼,有输出就先临时清掉,验证是不是代理导致的:
unset HTTP_PROXY HTTPS_PROXY ALL_PROXY清完再跑一次codex,如果 403 消失,那根因基本锁定在代理环境变量上,下面第四章还会专门展开。
3. 逐步定位403:从终端到请求链路的排查实操
3.1 第一步:先还原一个干净的SSH会话
排查 403 最忌讳的就是带病排查。我建议重新开一个 SSH 终端,把环境变量清到最干净的状态,再执行 Codex 相关命令,这样能把“当前环境变量污染”这个变量排除掉。
先执行环境清理:
unset HTTP_PROXY unset HTTPS_PROXY unset ALL_PROXY unset NO_PROXY unset CODEX_API_BASE_URL unset OPENAI_API_BASE然后确认 Codex 版本和登录状态:
codex --version codex login status如果codex login status显示未登录或 token 过期,那就重新登录一次。在 SSH 远程终端里大概率会走设备码模式,耐心把输出里的 URL 和设备码拿到浏览器里确认。如果codex login status显示已登录但实际请求依然 403,那就要进入下一步,直接抓请求链路。
这里有一个细节值得强调:不要在用sudo或su切换用户后的会话里排查。不同用户的 HOME 目录不同,sudo之后 Codex 读取的可能是 root 用户的~/.codex,而不是你自己的配置。我踩过一次坑,普通用户明明登录成功了,切到 root 用户后直接 403,就是因为 root 目录下根本没有有效的 auth.json。
3.2 第二步:用curl直连确认403的来源
这一步是整个排查过程里最关键的环节。不要盯着 CLI 那一行报错瞎猜,直接用 curl 模拟一次请求,看服务端到底认不认你的 token 和 base_url。
先取到 auth.json 里的访问令牌,然后手动发一个请求:
TOKEN=$(python3 -c "import json; print(json.load(open('/home/user/.codex/auth.json'))['tokens']['access_token'])") curl -i https://api.openai.com/v1/models \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json"要注意把上面的访问地址替换成你config.toml里base_url的实际地址。如果 curl 直连返回 200,说明 token、base_url、出口 IP 都没问题,问题藏在 Codex CLI 自己的运行环境里,重点查环境变量和本地代理。如果 curl 直连也返回 403,那问题就很明确了:要么 token 无效,要么出口 IP 被服务端策略拒绝,要么账号没有 Codex 权限。
针对 Codex 用到的/responses端点,也可以构造一个最小请求体来做直连测试:
curl -i https://api.openai.com/v1/responses \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"model":"gpt-5-codex","input":"hello"}'看返回体的状态码和错误信息。很多情况下返回体里会有一行error描述,比如insufficient_quota、invalid_request_error、access_denied,这些描述比单纯的 403 有价值得多。把返回体里的错误信息记下来,再去服务商文档里找对应的解决方案。
3.3 第三步:六类根因与对应解决方案
结合我处理过的案例,把 Codex 远程环境 403 的根因归纳成六类,每一类都给出验证方式和解决方案:
| 根因分类 | 典型现象 | 快速验证方式 | 解决方案 |
|---|---|---|---|
| token 过期或损坏 | 之前能用,突然 403 | 查看auth.json里的expires_at | 重新执行codex login |
| base_url 被覆盖 | 请求打到错误地址 | env grep -i base | 移除环境变量,恢复config.toml |
| 代理网关拦截 | 报错带local proxy failed | 临时 unset 代理变量后重试 | 清理代理配置或放行 API 域名 |
| 服务器时间不同步 | 刚配置好就 403 | 执行date对比标准时间 | 启用 NTP 时间同步 |
| 账号权限不足 | 新账号或未开通功能 | 官网确认订阅状态 | 联系服务商开通对应权限 |
| 配置文件权限异常 | 共享服务器上常见 | ls -l ~/.codex/auth.json | chmod 600 ~/.codex/auth.json |
时间不同步这个点容易被忽略。Codex 的令牌通常采用 JWT 结构,服务端校验时会对比签发时间和当前时间,如果服务器本地时间偏差超过容差,服务端会判定令牌无效并返回 403。在远程服务器上执行一下date,如果明显不对,就同步时间:
sudo timedatectl set-ntp true sudo systemctl restart systemd-timesyncd有跳板机或者多级 SSH 的情况下,还要检查每一层跳板机的系统时间,不要只在最终目标服务器上同步时间。
4. 专项解析“cc switch local proxy failed”报错
4.1 这个报错到底在说什么
看到cc switch local proxy failed while handling codex endpoint /responses这行提示时,很多人的第一反应是“我是不是被封了”。从实际定位的结论来看,这行提示说的是 Codex CLI 在内部处理/responses这个端点时,试图使用或切换一种本地代理模式,但切换动作失败了。注意,这是 CLI 本地这一层的错误,不是服务端返回的业务错误码。它是在请求还没真正发出去或者响应还没正常回来之前,本地组件自己先中断了。
出现这个报错的触发条件,通常跟当前会话里的代理环境变量有关。比如HTTPS_PROXY指向了一个代理端口,而那个代理服务其实没有启动;或者系统里配置了多级代理,Codex 的本地组件在处理时发生冲突;又或者NO_PROXY变量里没有把 API 域名加进去,导致应走直连的请求被强行塞给了代理。代理那头返回 403 或者连接失败,反映到 CLI 上就成了这行报错。
我在处理一台共享开发机时遇到过,那台机器的/etc/environment里被运维统一配置了HTTP_PROXY指向公司内网代理,而代理规则没有放行 Codex 的 API 域名,跑任何 Codex 命令都报这个错。单独看报错信息会觉得无从下手,但只要意识到这是“代理切换失败”,思路就清晰了。
4.2 绕过本地代理切换失败的三种做法
针对这个报错,我提供三种从轻到重的处理方式,建议按顺序试。
第一种,临时清掉当前会话的代理环境变量,验证是不是代理导致的:
unset HTTP_PROXY unset HTTPS_PROXY unset ALL_PROXY codex如果命令正常执行,说明就是代理环境变量在捣乱。
第二种,如果你确实需要走代理才能访问 API,那不要清空代理,而是把 API 域名加进NO_PROXY,让这部分请求绕过代理直连:
export NO_PROXY="api.openai.com,localhost,127.0.0.1,10.0.0.0/8"这里要注意NO_PROXY变量只对不要求代理的域名生效,如果你的网络出口本身就必须走代理才能访问外网,那直连也可能失败,需要结合你自己的网络环境来判断。
第三种,检查代理服务本身是否在运行、是否配置了对/responses端点的放行规则。很多本地代理工具有按域名或路径匹配的规则,如果规则里没有放行 Codex 的 API 域名,请求就会被拦截甚至直接返回 403。这时候应该去改代理工具的放行规则,或者把 Codex 进程从代理工具的接管范围里排除掉。
4.3 为什么别急着重装Codex
遇到 403 就rm -rf ~/.codex然后重新安装,这是我最不建议的做法。Codex 的 403 问题大概率不是软件损坏,而是配置、环境、网络三者之间出了错。重装 Codex 本身不会改变环境变量,不会改变代理配置,也不会改变账号权限,所以该 403 的还是 403,甚至因为删掉了原本有效的 auth.json,还得额外再登录一次。
我在过去踩过这个坑:改了一下午配置没解决,最后把.codex目录删了重新登录,结果还是报 403。静下心排查才发现是HTTPS_PROXY指向的代理端口早就不服务了,跟 Codex 程序本身半点关系都没有。代码块报错里只要带local proxy、endpoint这类词,第一时间就该往代理和端点配置方向查,重装解决不了问题,只会浪费时间。
5. 实战避坑经验与简化排查法
5.1 三个我踩过的坑和注意事项
第一,不要在共享开发机上随意覆盖全局代理配置。有时候你自己是临时排查设置了一个代理变量,结果忘了清理,下次别人登录这台机器,也会自动带上这个代理设置,Codex 直接 403。我在一台多人共用的服务器上就背过这个锅。排查完一定要记得恢复现场,或者至少用unset清掉临时变量。如果必须写进配置文件,建议写进当前用户的~/.bashrc,不要写/etc/environment。
第二,注意区分管理员登录和普通用户登录。很多开发机有多个账号,或者你平时习惯用sudo su切 root。Codex 的配置是按用户目录隔离的,你在普通用户的~/.codex/auth.json里登录的 token,root 用户压根读不到。所以如果切换用户后出现 403,先确认当前 HOME 目录下有没有有效的配置文件再继续排查。
第三,认真看 403 响应体的内容。HTTP 403 只是一个状态码,实际原因藏在响应体里。curl 直连测试时不要只盯着403三个数字,把响应体里的error字段读出来,通常会有access_denied、invalid_token、model_not_found这类明确描述。带着描述去搜解决方案,比带着一个 403 去搜效率高得多。
5.2 后续维护建议:四步快速排查法
如果你以后在远程服务器上再遇到 Codex 403 问题,我建议按下面四步走,不要乱:
第一步,确认 CLI 本身可用。执行codex --version,如果 CLI 本身都报错,那先处理安装问题。
第二步,确认实际生效的配置来源。执行env | grep -i -E "codex|openai|proxy",同时查看config.toml里的base_url。搞清楚 CLI 到底在往哪个地址发请求。
第三步,用 curl 直连模拟一次请求。拿auth.json里的 token 直接请求 API 端点,看 403 是服务端返回的还是 CLI 本地产生的。
第四步,针对性地清理环境变量或重新登录。curl 报 403 就处理 token 和账号权限,curl 正常但 CLI 报错就处理代理和环境变量。
这四步能覆盖九成以上的 403 场景。整套流程跑下来一般十分钟以内,比盲目重装快得多。
5.3 403错误排查速查表
最后给一张速查表,遇到问题直接查表定位,不用记整篇文章:
| 报错现象 | 优先怀疑方向 | 验证命令 | 解决方法 |
|---|---|---|---|
| 登录时 403 | 设备码过期、账号权限 | codex login重试 | 重新获取设备码或开通权限 |
| 执行任务时 403 | token 过期、base_url 错误 | curl -i直连 | 重新登录或修正 base_url |
报错含local proxy failed | 代理环境变量异常 | env grep -i proxy | unset 代理变量或添加 NO_PROXY |
| 突然全部 403 | 时间不同步 | date | 启用 NTP 时间同步 |
| 切换用户后 403 | HOME 目录配置缺失 | echo $HOME | 切换到有配置的用户登录 |
| 多用户共享机 403 | 配置文件权限 | ls -l ~/.codex/auth.json | chmod 600修复权限 |
在我实际处理过的案例里,远程服务器上的 Codex 403 问题,真正属于服务端封禁的概率很低,绝大多数都是环境和配置问题。最典型的就是代理变量残留,其次就是 base_url 被第三方教程误导改错,再就是 token 过期没人注意。把这几个方向排查完,问题基本就迎刃而解了。
我个人现在在 SSH 登录脚本里都会加一行提示,把当前环境变量里的代理相关值打印出来,登录时先看一眼,心里有数。这个习惯帮我避免了好几次“咦,怎么又 403 了”的困惑。你也试试,排查效率会明显不一样。