Skip to content

[Provider compatibility] Codex 0.155 access_programs is forwarded to strict third-party Responses upstreams and 400s every OpenCode Go muse-spark turn #4853

Description

@jhl880617

Client or integration

Codex App. Codex runtime codex-cli 0.155.0-alpha.2.6 (shipped with ChatGPT.app, installed 2026-09-17 10:03, previously 0.154.0-alpha.6.2). Also reproduced with minimal direct HTTP requests against the proxy and against the upstream.

Provider or upstream service

OpenCode Go (OpenCode-Go, https://opencode.ai/zen/go/v1) — the gateway that identifies itself as "Console Go" in the error message. Affects muse-spark-1.3-contributor and muse-spark-1.2-contributor; the same request shape succeeds on other models behind the same base URL.

OpenCodex version

2.57.0 (installed CLI, latest on npm at the time of writing). macOS, Apple Silicon. Observed 2026-09-17.

Endpoint or capability

Inbound POST /v1/responses on the proxy → outbound Responses passthrough to /zen/go/v1/responses (adapter: openai-responses, OpenCode-Go provider). Request body shape / unknown-parameter handling.

Current behaviour

Every routed turn to muse-spark-1.3-contributor fails with HTTP 400:

Error from provider (Console Go): Upstream request failed: [invalid_request_error] unknown parameter `access_programs`

Root cause: Codex 0.155 attaches a new OpenAI-private top-level field, access_programs, to its Responses request body. The client gates it on ChatGPT auth, not on the destination's base URL (codex-rs/core/src/cyber_access_program.rs):

pub(crate) fn for_auth(auth: Option<&CodexAuth>, program: Option<CyberAccessProgram>) -> Option<AccessPrograms> {
    program.filter(|_| auth.is_some_and(CodexAuth::is_chatgpt_auth)).map(AccessPrograms::from)
}

Because loopback injection keeps Codex's built-in openai provider pointed at the proxy (openai_base_url + realtime sideband override), the client still believes it is talking to the canonical ChatGPT backend and keeps the field. There is no client-side switch to suppress it, and it is not conditional on the model being requested.

The proxy forwards it verbatim, so whether a turn fails depends only on how strict the destination is:

Destination model (same provider, same wire) Unknown top-level field tolerated?
muse-spark-1.3-contributor No → 400
muse-spark-1.2-contributor No → 400
deepseek-v4.1-flash Yes → 200
grok-4.6 Yes → 200
glm-5.3 Yes → 200

Expected behaviour

Top-level fields that only OpenAI-operated destinations define should not be forwarded to third-party Responses upstreams. The proxy already does this for the per-item private field internal_chat_message_metadata_passthrough (stripInternalChatMessageMetadataPassthrough) at exactly the same boundary; the top-level private list needs the equivalent treatment.

Minimal redacted request or reproduction

# Fails: 400 unknown parameter `access_programs`
curl -sS http://127.0.0.1:10100/v1/responses \
  -H 'Content-Type: application/json' \
  -d '{"model":"OpenCode-Go/muse-spark-1.3-contributor","input":"hi","stream":false,"access_programs":{"cyber":"standard"}}'

# Succeeds: 200 (identical body, field omitted)
curl -sS http://127.0.0.1:10100/v1/responses \
  -H 'Content-Type: application/json' \
  -d '{"model":"OpenCode-Go/muse-spark-1.3-contributor","input":"hi","stream":false}'

The same body sent straight to the upstream, bypassing OpenCodex, isolates the field as the cause:

curl -sS https://opencode.ai/zen/go/v1/responses \
  -H "Authorization: Bearer $OPENCODE_GO_API_KEY" \
  -H 'x-opencode-session: <any-session-id>' \
  -H 'Content-Type: application/json' \
  -d '{"model":"muse-spark-1.3-contributor","input":"hi","access_programs":{"cyber":"standard"}}'

Actual response or error

{"model":"muse-spark-1.3-contributor","error":{"param":"access_programs","type":"invalid_request_error","message":"Error from provider (Console Go): Upstream request failed: [invalid_request_error] unknown parameter `access_programs`"}}

Probe results with the same minimal body against /zen/go/v1/responses for muse-spark-1.3-contributor:

Extra top-level field Result
access_programs: {"cyber":"standard"} 400, names access_programs
codex_output_schema: {"type":"object"} 400, names codex_output_schema
totally_made_up_param: 1 400, names totally_made_up_param
client_metadata, prompt_cache_key, service_tier, safety_identifier 200
(none) 200

So this upstream validates the top-level response schema strictly, and the only reason the failures started today is the new client field.

Upstream documentation

No public specification defines access_programs; it appears to be OpenAI-internal (cyber access program), so a third-party gateway treating it as unknown is correct.

Suggested mapping or implementation notes

Validated locally against 2.57.0, in the same place the per-item private field is already stripped:

  1. src/adapters/openai-responses/request-strips.ts — add stripOpenAiPrivateClientParams(body) that deletes access_programs and codex_output_schema, returns the input object unchanged when neither key is present (so the common path stays allocation-free), and passes non-objects through.
  2. src/adapters/openai-responses/passthrough.ts — inside the existing if (!isCanonicalOpenAiForwardProvider(provider)) block, next to stripInternalChatMessageMetadataPassthrough(outBody), call it when !isOpenAiOperatedResponsesDestination(provider) so official OpenAI / ChatGPT traffic keeps the field and only third-party destinations lose it.

Verification after patching: muse-spark turns return 200 with the field present in the incoming body; a genuinely invalid parameter still returns 400 (so the change does not mask real client errors); deepseek-v4.1-flash, grok-4.6 and glm-5.3 are unaffected; the incoming raw body object is not mutated.

Note: forcing muse-spark onto the openai-chat wire is not an alternative workaround — POST /zen/go/v1/chat/completions for muse-spark-1.3-contributor returns HTTP 500 {"type":"error","error":{"type":"error","message":"Internal server error"}} (observed the same day, both via the proxy and directly), so the Responses wire is the only working one for these models.

Additional context and attachments

  • Checked for duplicates: no existing issue or PR mentions access_programs, codex_output_schema, or Codex 0.155.
  • Requests and responses above are redacted: no credentials, tokens, account labels, or conversation content are included.
  • The field is version-dependent (access_programs is new in Codex 0.155; requests from Codex 0.154 and earlier did not contain it) and the same Codex build works fine on providers whose validator is lenient, which is why this only broke today and only for the strict Responses backends.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    account-poolOAuth, credentials, Codex pool, quota, failover, plansproviderProvider adapters, OpenAI-compat presets, upstream API quirksprovider-compatibilityProvider compatibility reports

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions