Skip to content

Codex subscription proxy forwards Responses bodies the ChatGPT backend rejects (max_output_tokens / missing instructions → HTTP 400) #39

Description

@skulidropek

Summary

When UPSTREAM_PROVIDER=codex, the subscription proxy forwards the client's /v1/responses body to the ChatGPT Codex backend (https://chatgpt.com/backend-api/codex/responses) verbatim (only stream is forced to true in src/subscription_proxy.rs). But the Codex backend is stricter than the generic OpenAI Responses API and rejects two things that standard OpenAI Responses clients routinely send/omit:

  1. max_output_tokens is rejectedHTTP 400 {"detail":"Unsupported parameter: max_output_tokens"}
  2. instructions is requiredHTTP 400 {"detail":"Instructions are required"}

As a result, off-the-shelf OpenAI Responses clients cannot use a Codex subscription through the router — every request fails with a 400.

Environment

  • Router v0.20.0 (image konard/link-assistant-router:latest)
  • UPSTREAM_PROVIDER=codex, credential ~/.codex/auth.json (ChatGPT OAuth), valid token (doctor → "codex subscription … (found, token OK)")

Reproduction

A standard OpenAI Responses request (this is exactly what the OpenClaw agent emits for an openai-responses provider):

curl -s "$ROUTER/v1/responses" \
  -H "Authorization: Bearer la_sk_..." -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-5.5",
    "input": [{"role":"user","content":[{"type":"input_text","text":"hi"}]}],
    "stream": true,
    "store": false,
    "max_output_tokens": 8192,
    "reasoning": {"effort": "none"}
  }'
# → 400 {"detail":"Unsupported parameter: max_output_tokens"}

Remove max_output_tokens but still omit instructions:

# → 400 {"detail":"Instructions are required"}

Add instructions and drop max_output_tokens:

-d '{"model":"gpt-5.5","instructions":"You are a test echo.","input":[{"role":"user","content":[{"type":"input_text","text":"Reply with exactly: ROUTER_OK"}]}],"store":false}'
# → 200, streamed response "ROUTER_OK"  ✅

So the upstream/credential/transport are all fine — the only problem is that the router forwards a body shape the Codex backend won't accept.

Root cause

src/subscription_proxy.rs::forward_subscription_openai only does:

if provider == SubscriptionProvider::Codex {
    body["stream"] = serde_json::Value::Bool(true);
}

It does not strip max_output_tokens or ensure instructions is present. (Note src/responses.rs::chat_completion_to_responses even adds max_output_tokens when projecting Chat Completions → Responses, which then hits the same 400 for the /v1/chat/completions path.)

Proposed fix

For Codex, normalize the Responses body before forwarding: force stream, strip max_output_tokens, and inject a default instructions only when the caller omitted one (preserving caller intent otherwise). PR incoming.

This makes any standard OpenAI Responses / Chat Completions client (OpenClaw, SDKs, etc.) work against a Codex subscription through the router.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions