跳到正文
fomoxx.A PERSONAL CORNER OF THE INTERNET

Fix Codex App instructions_required 400 Behind CLIProxyAPI

AI Agents

中文版

Fix Codex App instructions_required 400 Behind CLIProxyAPI

When I routed the Codex App through CLIProxyAPI (CPA) to a third-party upstream, requests failed immediately with a 400 error:

instructions_required · 400
The instructions parameter is required and must be a non-empty string.

The failure was not fixed by upgrading CPA, turning on the built-in Codex instructions option, or trying to remove the field with payload.filter. The working fix was much smaller: keep the built-in injection disabled and use payload.default to supply a minimal non-empty compatibility value only when the field is missing.

Quick fix: the configuration that worked

In /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."

Then restart the CPA container:

cd /opt/cpa-deploy
docker compose restart cli-proxy-api

After the restart, the Codex App worked again through this upstream.

Environment

ItemVersion / setup
DeploymentDocker Compose
CPA image after upgradeeceasy/cli-proxy-api:v7.2.147
Containercpa-cli-proxy-api
Compose file/opt/cpa-deploy/docker-compose.yml
CPA config/opt/cpa-deploy/config.yaml
Codex App0.152.1
Endpoint/v1/responses

CPA had originally been on v7.2.142; I upgraded to v7.2.147 during troubleshooting.

Root cause

The important part was the shape of the request at each layer.

1. What the Codex App sent

The newer Codex App request did not include a top-level instructions field. Its system/developer instructions were already carried inside input.

2. What CLIProxyAPI normalized

While forwarding the Codex /v1/responses request, CPA normalized the missing top-level field into:

"instructions": ""

3. What this upstream accepted

In the third-party upstream I was using, validation behaved like this:

  • field missing: accepted
  • non-empty string: accepted
  • field present as an empty string: rejected with instructions_required

So the problem was not “Codex forgot its instructions.” The compatibility failure was created by the combination of CPA’s normalization behavior and the upstream’s stricter validation of an explicitly empty value.

What I tried before the working fix

Attempt 1: upgrade CLIProxyAPI

I first upgraded CPA from v7.2.142 to v7.2.147:

cd /opt/cpa-deploy
docker compose pull
docker compose up -d

The error remained. That ruled out the simple explanation that I was only hitting an old-version bug already fixed by the newer image.

Attempt 2: enable Codex instructions injection

I tried:

codex-instructions-enabled: true

That still did not resolve this upstream-specific failure. I ultimately left the option disabled.

There was another reason not to keep it enabled: it injects a much larger default Codex prompt, while this compatibility problem only required the top-level field to be non-empty.

Attempt 3: remove the empty field with payload.filter

Because the upstream accepted a completely missing field, deleting instructions seemed cleaner than filling it.

I tried filtering the empty field, but the final request still contained:

"instructions": ""

The reason was a later CPA normalization step. Even if the field was filtered earlier in the pipeline, it was added back as an empty string before the request reached the upstream.

That made “remove it” an ineffective strategy for this path.

Final approach: fill a minimal value only when missing

The reliable workaround was:

codex-instructions-enabled: false

payload:
  default:
    - models:
        - name: "gpt-*"
          protocol: "codex"
      params:
        "instructions": "Follow the instructions provided in the input."

This satisfied the upstream’s requirement without asking CPA to inject its full default Codex prompt.

Why payload.default was a better fit than override

Two details mattered to me.

First, the actual Codex system/developer instructions were already present in the input structure. In this setup, the short top-level value existed to satisfy an upstream compatibility check rather than to replace the real instruction context.

Second, I used payload.default instead of payload.override. If a future client request already contains a valid instructions value, a default does not need to forcefully replace it.

The resulting data flow was:

Codex App
System/developer instructions are already in input
CPA sees top-level instructions missing
payload.default supplies a minimal non-empty value
Third-party validation passes
Request executes normally

A faster way to debug similar 400 errors

The most useful evidence was in the CPA logs. Compare these two sections:

  1. REQUEST BODY — what the Codex App originally sent to CPA.
  2. API REQUEST 1 — what CPA actually sent to the upstream after its own processing.

If the upstream rejects a request and those two bodies are structurally different, the proxy’s translation or normalization layer becomes a much stronger suspect than the client itself.

This is the same debugging habit I used in my local MCP to ChatGPT Web setup : validate each layer independently, then compare what crosses the boundary between layers.

What this fix does — and does not claim

This is a compatibility fix for the concrete chain I tested: Codex App → CLIProxyAPI → a third-party Responses upstream with the validation behavior described above.

It does not mean every instructions_required error has the same cause. The useful general method is to inspect the request before and after the proxy transformation rather than assuming the client, proxy, or model endpoint is at fault.

FAQ

What caused the Codex App 400 instructions_required error behind CLIProxyAPI?

The Codex App request did not contain a top-level instructions field, while CLIProxyAPI normalized the missing field to an empty string. The third-party upstream accepted a missing or non-empty value but rejected an explicitly empty instructions string.

Why didn't codex-instructions-enabled fix it?

Enabling it injects a larger default Codex prompt and did not resolve this upstream-specific validation case. I kept it false and used a minimal compatibility value instead.

Why couldn't payload.filter remove instructions?

A later normalization step in CLIProxyAPI added the field back as an empty string, so filtering it earlier did not change the final upstream request.

What payload.default configuration worked?

I kept codex-instructions-enabled false and added a short non-empty instructions value for gpt-* models using the codex protocol: Follow the instructions provided in the input.

How should similar 400 errors be debugged?

Compare REQUEST BODY, the request received from the client, with API REQUEST 1, the request CLIProxyAPI actually sends upstream. Structural differences between those two layers can expose proxy translation or normalization problems.