跳到正文
fomoxx.

Codex App 经 CLIProxyAPI 接入中转站报 400(instructions_required)的排障与修复

English version

Codex App 经 CLIProxyAPI 接入中转站报 400(instructions_required)的排障与修复

在将 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 生成的空参数与该中转站的校验规则发生了冲突:

  1. 客户端行为:新版 Codex App 的请求本身并不包含顶层的 instructions 字段,系统与开发指令已经被放入了 input 中。
  2. CPA 的规范化处理:CPA 在转发 Codex 的 /v1/responses 请求时,会把缺失的 instructions 自动规范化成一个空字符串:
    "instructions": ""
    
  3. 中转站的校验机制:官方 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 的错误日志中定位并对比以下两个段落:

  1. REQUEST BODY:Codex Desktop 客户端发送给 CPA 的原始请求体。
  2. API REQUEST 1:CPA 经过内部处理后,实际发往上游中转站的请求体。

如果对比发现这两个部分的字段结构或取值不一致,问题通常发生在 CPA 自身的请求翻译或规范化阶段,而不是 Codex Desktop。

这种“先验证每一层,再对比层与层之间的输入输出”的方法,也用在了本地 MCP 接入 ChatGPT Web 的排障 中。那里的断点是认证、Server、Tunnel 和客户端,具体修复方式不同,但拆分链路的思路相同。

常见问题

Codex App 接入中转站报 400 instructions_required 的根因是什么?

新版 Codex App 将指令存放在 input 中,未在顶层传递 instructions 字段。CPA 转发 /v1/responses 请求时自动补齐了空字符串 “instructions”: “",而部分第三方中转站只接受完全缺失或非空字符串,对空字符串直接返回 400 instructions_required 报错。

为什么开启 codex-instructions-enabled 没有用?

该配置会注入大段默认 Codex 提示词,不仅可能仍然无法通过中转站特定校验,还额外增加了 Prompt 消耗。保持其为 false 更加干净可控。

为什么不能通过 payload.filter 移除 instructions?

CPA 在内部请求处理流程中存在后置的规范化机制,即使使用 filter 移除了空 instructions 字段,后续流程依然会自动补回空字符串 “",因此无法通过剔除字段来解决。

如何通过 payload.default 正确配置修复?

在 CPA 的 config.yaml 中保持 codex-instructions-enabled: false,并在 payload.default 中为 gpt-* 且 protocol 为 codex 的模型注入一句简短的非空占位指令(如 “Follow the instructions provided in the input."),保存后重启 CPA 容器即可恢复。

后续遇到类似 400 报错如何快速定位?

在 CPA 日志中对比 REQUEST BODY(客户端发送的原生请求)与 API REQUEST 1(CPA 发往上游的请求)。若两者结构差异导致拒绝,说明是代理层的转换或规范化逻辑触发了上游校验规则。

继续阅读

评论