You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
AI2API - an internal corporate gateway speaking the OpenAI Responses API (provider config: adapter: "openai-responses", authMode: "key"). The endpoint is internal-only; there is no public hostname or public specification for the gateway. Its WAF rejects any request that carries no User-Agent header with HTTP 403 and an empty body.
OpenCodex version
2.59.0 (npm @bitkyc08/opencodex). Source-checked against the installed package: src/oauth/index.tsbuildModelsRequest() sets only Authorization: Bearer ... for this adapter shape (plus registry static headers where applicable), and src/lib/provider-outbound.ts executes it through pinnedHttpGet() in src/lib/pinned-http.ts, a Node-style node:https request that adds no default User-Agent.
Endpoint or capability
Model discovery (GET {baseUrl}/models).
Current behaviour
After adding the provider, no models ever appear: the Models page shows no rows for it and the Codex catalog gains no entries. On 2.59.0 a newly registered key-auth provider starts with initialModelSelection.status: "pending", and a pending provider has all of its model rows hidden, so the visible symptom is "provider added but invisible" with no pointer to the discovery failure in the UI.
service.log repeats on every discovery pass:
[opencodex] Provider model discovery for "AI2API" failed with HTTP 403 [urlClass=provider-models, fallback=configured].
Root cause as observed: the gateway's WAF requires a User-Agent on every request. The discovery fetch is proxy-originated, so there is no client request to inherit a UA from, and the pinned Node-style transport sends none. Controlled comparison from the same machine, same credential, and same resolved address:
curl (sends User-Agent: curl/x) - HTTP 200 with the full model list
Bun fetch (sends User-Agent: Bun/x) - HTTP 200
Node-style https.request with only Authorization: Bearer ... (what discovery sends today) - HTTP 403, empty body
The same Node-style request plus any User-Agent value - HTTP 200
Inference through the proxy to the same provider works, consistent with #4699/#4702: applyCallerUserAgentFallback preserves the caller's User-Agent on upstream Responses requests. The gap is specific to the proxy-originated /models discovery fetch, which that fix cannot cover by design (no caller to inherit from).
Expected behaviour
Model discovery should succeed against gateways that require a User-Agent, or at minimum the failure should name the likely cause. Since discovery requests are proxy-originated rather than client-proxied, sending a default User-Agent on this path does not conflict with the wire-compatibility rationale in #1751, which declined a global product UA for client-fingerprinted traffic.
Transport difference (redacted; same host, same credential, same resolved address):
# 1) curl - default UA present - HTTP 200
curl -H "Authorization: Bearer <redacted>" https://gateway.internal.example/v1/models
# 2) Node-style request with Authorization only - what discovery sends today# headers = { "Authorization": "Bearer <redacted>" } -> HTTP 403, empty body# 3) Same Node-style request plus any User-Agent - HTTP 200# headers = { "Authorization": "Bearer <redacted>", "User-Agent": "opencodex" }
Workaround verified on 2.59.0 - a static provider header is merged into both discovery and inference requests:
{
"headers": { "User-Agent": "opencodex" }
}
After this change discovery succeeded (54 models), initialModelSelection converged from pending to all-off (54 ids is above the 20-model threshold, so they all default to off), and the enabled models appeared in the live /v1/models and the Codex catalog.
Actual response or error
[opencodex] Provider model discovery for "AI2API" failed with HTTP 403 [urlClass=provider-models, fallback=configured].
The gateway's 403 body is empty (WAF-style rejection). No other error is surfaced anywhere; the provider simply has no models.
Upstream documentation
No public specification exists for the gateway (internal corporate deployment). The requirement was established empirically by the controlled client comparison above: requests without a User-Agent header are rejected with 403; requests with any User-Agent value are accepted. UA-based filtering is a common WAF/CDN posture, so this is not specific to one vendor.
[Feature] Add an OpenCodex-specific User-Agent header #1751 declined a global opencodex/x.y.z UA for client-proxied traffic to preserve client fingerprints. Discovery is not client-proxied traffic, so a default UA on this path (e.g. opencodex, matching the existing convention in src/oauth/github-copilot.ts, or opencodex/<version>) would not touch any client-fingerprinted path. A provider-configured headers value should still win, as it does today.
Lower-cost alternatives if a default UA is still unwanted:
When discovery fails with 403, mention the UA-requiring-gateway case. POST /api/providers/test currently reports only upstream model discovery returned 403, which gives no hint.
Document/surface the provider headers workaround for WAF-fronted gateways in the provider form or docs.
The initial-model-selection gating amplified the confusion: a pending provider hides all rows, so the symptom looks like "the provider has no models" rather than "discovery is being rejected".
Checks
I searched existing provider and compatibility issues.
The request and response were redacted.
The expected behaviour is based on an upstream specification or a concrete client requirement.
Client or integration
Codex App
Provider or upstream service
AI2API - an internal corporate gateway speaking the OpenAI Responses API (provider config:
adapter: "openai-responses",authMode: "key"). The endpoint is internal-only; there is no public hostname or public specification for the gateway. Its WAF rejects any request that carries noUser-Agentheader with HTTP 403 and an empty body.OpenCodex version
2.59.0 (npm
@bitkyc08/opencodex). Source-checked against the installed package:src/oauth/index.tsbuildModelsRequest()sets onlyAuthorization: Bearer ...for this adapter shape (plus registry static headers where applicable), andsrc/lib/provider-outbound.tsexecutes it throughpinnedHttpGet()insrc/lib/pinned-http.ts, a Node-stylenode:httpsrequest that adds no defaultUser-Agent.Endpoint or capability
Model discovery (
GET {baseUrl}/models).Current behaviour
After adding the provider, no models ever appear: the Models page shows no rows for it and the Codex catalog gains no entries. On 2.59.0 a newly registered key-auth provider starts with
initialModelSelection.status: "pending", and a pending provider has all of its model rows hidden, so the visible symptom is "provider added but invisible" with no pointer to the discovery failure in the UI.service.logrepeats on every discovery pass:Root cause as observed: the gateway's WAF requires a
User-Agenton every request. The discovery fetch is proxy-originated, so there is no client request to inherit a UA from, and the pinned Node-style transport sends none. Controlled comparison from the same machine, same credential, and same resolved address:curl(sendsUser-Agent: curl/x) - HTTP 200 with the full model listfetch(sendsUser-Agent: Bun/x) - HTTP 200https.requestwith onlyAuthorization: Bearer ...(what discovery sends today) - HTTP 403, empty bodyUser-Agentvalue - HTTP 200Inference through the proxy to the same provider works, consistent with #4699/#4702:
applyCallerUserAgentFallbackpreserves the caller'sUser-Agenton upstream Responses requests. The gap is specific to the proxy-originated/modelsdiscovery fetch, which that fix cannot cover by design (no caller to inherit from).Expected behaviour
Model discovery should succeed against gateways that require a
User-Agent, or at minimum the failure should name the likely cause. Since discovery requests are proxy-originated rather than client-proxied, sending a defaultUser-Agenton this path does not conflict with the wire-compatibility rationale in #1751, which declined a global product UA for client-fingerprinted traffic.Minimal redacted request or reproduction
Provider config (redacted):
{ "AI2API": { "adapter": "openai-responses", "baseUrl": "https://gateway.internal.example/v1", "authMode": "key", "allowPrivateNetwork": true } }Transport difference (redacted; same host, same credential, same resolved address):
Workaround verified on 2.59.0 - a static provider header is merged into both discovery and inference requests:
{ "headers": { "User-Agent": "opencodex" } }After this change discovery succeeded (54 models),
initialModelSelectionconverged frompendingtoall-off(54 ids is above the 20-model threshold, so they all default to off), and the enabled models appeared in the live/v1/modelsand the Codex catalog.Actual response or error
The gateway's 403 body is empty (WAF-style rejection). No other error is surfaced anywhere; the provider simply has no models.
Upstream documentation
No public specification exists for the gateway (internal corporate deployment). The requirement was established empirically by the controlled client comparison above: requests without a
User-Agentheader are rejected with 403; requests with anyUser-Agentvalue are accepted. UA-based filtering is a common WAF/CDN posture, so this is not specific to one vendor.Suggested mapping or implementation notes
opencodex/x.y.zUA for client-proxied traffic to preserve client fingerprints. Discovery is not client-proxied traffic, so a default UA on this path (e.g.opencodex, matching the existing convention insrc/oauth/github-copilot.ts, oropencodex/<version>) would not touch any client-fingerprinted path. A provider-configuredheadersvalue should still win, as it does today.POST /api/providers/testcurrently reports onlyupstream model discovery returned 403, which gives no hint.headersworkaround for WAF-fronted gateways in the provider form or docs.Additional context and attachments
pendingprovider hides all rows, so the symptom looks like "the provider has no models" rather than "discovery is being rejected".Checks