Codex App 经 CLIProxyAPI 接入中转站报 400(instructions_required)的排障与修复
2026-09-04T17:20:00+08:00

在将 Codex App 通过 CLIProxyAPI(CPA)接入第三方中转站时,遇到了请求直接失败的问题。服务端返回了 400 错误,提示缺少 instructions 参数。
如果你也遇到了完全相同的报错,可以先直接参考下面的可用配置进行修复;后文记录了具体的定位过程、失败尝试以及参数处理细节。
快速解决(最终可用配置)
编辑 CPA 的配置文件(/opt/cpa-deploy/config.yaml),加入以下配置项:
codex-instructions-enabled: false
payload:
default:
- models:
- name: "gpt-*"
protocol: "codex"
params:
"instructions": "Follow the instructions provided in the input."
修改保存后,进入部署目录重启 CPA 容器:
cd /opt/cpa-deploy
docker compose restart cli-proxy-api
重启完成后,Codex Desktop 恢复正常使用。
错误现象与环境信息
在 Codex App 发送请求后,接口直接返回如下 400 错误信息:
instructions_required · 400
The instructions parameter is required and must be a non-empty string.
本次涉及的运行环境与组件版本如下:
| 项目 | 配置 / 版本 |
|---|---|
| 部署方式 | Docker Compose |
| CPA 镜像 | eceasy/cli-proxy-api:v7.2.147 |
| 容器名 | cpa-cli-proxy-api |
| Compose 文件路径 | /opt/cpa-deploy/docker-compose.yml |
| CPA 配置文件 | /opt/cpa-deploy/config.yaml |
| Codex App 版本 | 0.152.1 |
| 请求接口 | /v1/responses |
问题原因
排查后发现,问题既不是 Codex App 客户端故障,也不是上游模型不可用,而是 CPA 生成的空参数与该中转站的校验规则发生了冲突:
- 客户端行为:新版 Codex App 的请求本身并不包含顶层的
instructions字段,系统与开发指令已经被放入了input中。 - CPA 的规范化处理:CPA 在转发 Codex 的
/v1/responses请求时,会把缺失的instructions自动规范化成一个空字符串:"instructions": "" - 中转站的校验机制:官方 OpenAI / Codex 链路对该字段的处理可以正常工作,但当前接入的额外中转站对
instructions设定了更严格的校验逻辑:- 字段完全缺失:允许
- 字段为非空字符串:允许
- 字段存在但值为空字符串(
""):直接拒绝并返回instructions_required
因此,正是 CPA 补齐的 "instructions": "" 触发了中转站的拒绝策略。
排障与尝试过程
在最终找到可用配置之前,我先后尝试了几种不同的解决方式:
1. 升级 CPA 镜像版本
最开始 CPA 运行的版本是 v7.2.142。我尝试拉取更新,升级到了 v7.2.147:
cd /opt/cpa-deploy
docker compose pull
docker compose up -d
升级完成后复测,报错依然存在。这说明该问题不是单纯由旧版本 Bug 引起的。
2. 尝试开启 Codex instructions 注入
查阅 CPA 配置后,尝试在 config.yaml 中开启指令注入:
codex-instructions-enabled: true
配置后重启,依然无法解决该中转站场景下的报错,因此最终将该项保持关闭(codex-instructions-enabled: false)。
3. 尝试通过 payload.filter 剔除空 instructions
既然中转站在字段缺失时允许通过,我曾尝试使用 payload.filter 将空的 instructions 字段直接过滤掉。
但在最终发给中转站的请求里,依然出现了:
"instructions": ""
这是因为 CPA 在处理 Codex 请求时存在后续的内部规范化流程,即便通过 filter 删除了该字段,后续流程依然会重新补回空字符串。因此“删除字段”的思路行不通。
4. 最终方案:缺失时补齐最小非空占位值
既然无法彻底移除该字段,又不能传递空字符串,可行的方案就是在字段缺失时代替它补入一个符合要求的最小非空字符串。
使用 payload.default 为 gpt-* 模型补入占位内容:
codex-instructions-enabled: false
payload:
default:
- models:
- name: "gpt-*"
protocol: "codex"
params:
"instructions": "Follow the instructions provided in the input."
保持 codex-instructions-enabled: false 可以避免 CPA 额外注入大段 Codex 提示词;而通过 payload.default 注入的一句简短占位文本,则顺利满足了中转站“instructions 不能是空字符串”的硬性校验。
方案原理解析与数据流向
为什么采用 payload.default 是最合适的?
- 不影响核心指令:真正的 Codex App 系统/开发指令已经在
input结构中传递,顶层的instructions在这里仅承担兼容中转站参数校验的作用。 - 保留客户端自主权:这里使用的是
payload.default而非强制覆盖的payload.override。这意味着如果后续客户端自身传递了正常的instructions,CPA 不会强行将其覆盖掉。
最终整个调用的数据流向如下:
Codex App
↓
系统/开发指令位于 input
↓
CPA 检查到 instructions 缺失
↓
payload.default 补齐最小非空占位值 ("Follow the instructions provided in the input.")
↓
中转站参数校验通过
↓
请求正常执行
后续排查类似 400 错误的提示
后续如果在使用 Codex App 经由 CPA 接入其他第三方 Responses 接口时再次遇到类似的 400 报错,建议在排查时优先在 CPA 的错误日志中定位并对比以下两个段落:
REQUEST BODY:Codex Desktop 客户端发送给 CPA 的原始请求体。API REQUEST 1:CPA 经过内部处理后,实际发往上游中转站的请求体。
如果对比发现这两个部分的字段结构或取值不一致,问题通常发生在 CPA 自身的请求翻译或规范化阶段,而不是 Codex Desktop。