detached b58450d

Use Codex CLI request shape for all ChatGPT subscription models

The ChatGPT Codex backend load-sheds requests from unrecognized
client identities with "Our servers are currently overloaded"
stream errors even when capacity is fine. Extend the existing
Codex CLI routing metadata (originator, user-agent, session-id,
prompt_cache_key) from preview models to every model on the
subscription backend. Public Responses API paths are unchanged.
commit
b58450d9a0577d71baa0007af889d6d6a5f6a8a8
author
patriceckhart · · 2026-07-30 10:05 UTC
parent
ad8d624
signature
unsigned
refs
tag: v0.3.28
2 files · +29 -11
M packages/provider/openai_codex.go +14 -5
@@ -45,6 +45,7 @@ type codexClient struct {
4545 providerName string
4646 modelName func(string) string
4747 disableCLIRouting bool
48+ cliRoutingAll bool
4849 http *http.Client
4950 }
5051
@@ -61,7 +62,14 @@ func NewOpenAICodex(token, accountID, baseURL string) Client {
6162 baseURL: strings.TrimRight(baseURL, "/"),
6263 errorLabel: "codex",
6364 providerName: "openai-codex",
64- http: &http.Client{Timeout: 0},
65+ // The ChatGPT Codex backend load-sheds requests from client
66+ // identities it does not recognize: unknown originator/user-agent
67+ // pairs receive "Our servers are currently overloaded" stream
68+ // errors near-deterministically while Codex CLI requests succeed.
69+ // Send the Codex CLI request shape for every model on this
70+ // backend.
71+ cliRoutingAll: true,
72+ http: &http.Client{Timeout: 0},
6573 }
6674 }
6775
@@ -363,7 +371,7 @@ func (c *codexClient) Stream(ctx context.Context, req Request) (<-chan Event, er
363371 wire.Model = c.modelName(wire.Model)
364372 }
365373 var codexCLISessionID string
366- if !c.disableCLIRouting && usesCodexCLIRouting(wire.Model) {
374+ if !c.disableCLIRouting && (c.cliRoutingAll || usesCodexCLIRouting(wire.Model)) {
367375 codexCLISessionID = newCodexSessionID()
368376 wire.PromptCacheKey = codexCLISessionID
369377 }
@@ -383,9 +391,10 @@ func (c *codexClient) Stream(ctx context.Context, req Request) (<-chan Event, er
383391 httpReq.Header.Set("chatgpt-account-id", c.accountID)
384392 httpReq.Header.Set("openai-beta", "responses=experimental")
385393 if codexCLISessionID != "" {
386- // Some preview models are only admitted or reliably served by the
387- // ChatGPT Codex backend when the request follows Codex CLI routing
388- // metadata. Keep this narrow so Sol retains zot's proven shape.
394+ // The ChatGPT Codex backend only admits (and reliably serves)
395+ // requests that follow Codex CLI routing metadata; other client
396+ // identities are load-shed with "servers are currently
397+ // overloaded" errors even when capacity is fine.
389398 httpReq.Header.Set("originator", "codex_cli_rs")
390399 httpReq.Header.Set("session-id", codexCLISessionID)
391400 httpReq.Header.Set("user-agent", "codex_cli_rs/0.0.0")
@@ -45,6 +45,7 @@ type codexClient struct {
45 providerName string45 providerName string
46 modelName func(string) string46 modelName func(string) string
47 disableCLIRouting bool47 disableCLIRouting bool
48+ cliRoutingAll bool
48 http *http.Client49 http *http.Client
49 }50 }
50 51
@@ -61,7 +62,14 @@ func NewOpenAICodex(token, accountID, baseURL string) Client {
61 baseURL: strings.TrimRight(baseURL, "/"),62 baseURL: strings.TrimRight(baseURL, "/"),
62 errorLabel: "codex",63 errorLabel: "codex",
63 providerName: "openai-codex",64 providerName: "openai-codex",
64- http: &http.Client{Timeout: 0},65+ // The ChatGPT Codex backend load-sheds requests from client
66+ // identities it does not recognize: unknown originator/user-agent
67+ // pairs receive "Our servers are currently overloaded" stream
68+ // errors near-deterministically while Codex CLI requests succeed.
69+ // Send the Codex CLI request shape for every model on this
70+ // backend.
71+ cliRoutingAll: true,
72+ http: &http.Client{Timeout: 0},
65 }73 }
66 }74 }
67 75
@@ -363,7 +371,7 @@ func (c *codexClient) Stream(ctx context.Context, req Request) (<-chan Event, er
363 wire.Model = c.modelName(wire.Model)371 wire.Model = c.modelName(wire.Model)
364 }372 }
365 var codexCLISessionID string373 var codexCLISessionID string
366- if !c.disableCLIRouting && usesCodexCLIRouting(wire.Model) {374+ if !c.disableCLIRouting && (c.cliRoutingAll || usesCodexCLIRouting(wire.Model)) {
367 codexCLISessionID = newCodexSessionID()375 codexCLISessionID = newCodexSessionID()
368 wire.PromptCacheKey = codexCLISessionID376 wire.PromptCacheKey = codexCLISessionID
369 }377 }
@@ -383,9 +391,10 @@ func (c *codexClient) Stream(ctx context.Context, req Request) (<-chan Event, er
383 httpReq.Header.Set("chatgpt-account-id", c.accountID)391 httpReq.Header.Set("chatgpt-account-id", c.accountID)
384 httpReq.Header.Set("openai-beta", "responses=experimental")392 httpReq.Header.Set("openai-beta", "responses=experimental")
385 if codexCLISessionID != "" {393 if codexCLISessionID != "" {
386- // Some preview models are only admitted or reliably served by the394+ // The ChatGPT Codex backend only admits (and reliably serves)
387- // ChatGPT Codex backend when the request follows Codex CLI routing395+ // requests that follow Codex CLI routing metadata; other client
388- // metadata. Keep this narrow so Sol retains zot's proven shape.396+ // identities are load-shed with "servers are currently
397+ // overloaded" errors even when capacity is fine.
389 httpReq.Header.Set("originator", "codex_cli_rs")398 httpReq.Header.Set("originator", "codex_cli_rs")
390 httpReq.Header.Set("session-id", codexCLISessionID)399 httpReq.Header.Set("session-id", codexCLISessionID)
391 httpReq.Header.Set("user-agent", "codex_cli_rs/0.0.0")400 httpReq.Header.Set("user-agent", "codex_cli_rs/0.0.0")
M packages/provider/openai_codex_test.go +15 -6
@@ -196,7 +196,13 @@ func TestCodexNestedStreamError(t *testing.T) {
196196 }
197197 }
198198
199-func TestCodexSolKeepsZotShape(t *testing.T) {
199+// TestCodexSubscriptionAlwaysUsesCodexCLIShape pins the ChatGPT
200+// subscription backend to the Codex CLI request identity for every
201+// model. The backend load-sheds unrecognized originator/user-agent
202+// pairs with "Our servers are currently overloaded" stream errors
203+// even when capacity is fine, so the CLI shape is required for
204+// reliable service, not just for preview-model admission.
205+func TestCodexSubscriptionAlwaysUsesCodexCLIShape(t *testing.T) {
200206 c := NewOpenAICodex("token", "acct", "https://example.test/backend-api/codex/responses").(*codexClient)
201207 var gotReq *http.Request
202208 var body bytes.Buffer
@@ -223,13 +229,16 @@ func TestCodexSolKeepsZotShape(t *testing.T) {
223229 if gotReq == nil {
224230 t.Fatal("request was not sent")
225231 }
226- if gotReq.Header.Get("originator") != "zot" {
232+ if gotReq.Header.Get("originator") != "codex_cli_rs" {
227233 t.Fatalf("originator = %q", gotReq.Header.Get("originator"))
228234 }
229- if gotReq.Header.Get("session-id") != "" {
230- t.Fatalf("session-id = %q", gotReq.Header.Get("session-id"))
235+ if gotReq.Header.Get("user-agent") != "codex_cli_rs/0.0.0" {
236+ t.Fatalf("user-agent = %q", gotReq.Header.Get("user-agent"))
237+ }
238+ if gotReq.Header.Get("session-id") == "" {
239+ t.Fatal("session-id header missing")
231240 }
232- if strings.Contains(body.String(), "prompt_cache_key") {
233- t.Fatalf("Sol request unexpectedly included prompt_cache_key: %s", body.String())
241+ if !strings.Contains(body.String(), "prompt_cache_key") {
242+ t.Fatalf("request missing prompt_cache_key: %s", body.String())
234243 }
235244 }
@@ -196,7 +196,13 @@ func TestCodexNestedStreamError(t *testing.T) {
196 }196 }
197 }197 }
198 198
199-func TestCodexSolKeepsZotShape(t *testing.T) {199+// TestCodexSubscriptionAlwaysUsesCodexCLIShape pins the ChatGPT
200+// subscription backend to the Codex CLI request identity for every
201+// model. The backend load-sheds unrecognized originator/user-agent
202+// pairs with "Our servers are currently overloaded" stream errors
203+// even when capacity is fine, so the CLI shape is required for
204+// reliable service, not just for preview-model admission.
205+func TestCodexSubscriptionAlwaysUsesCodexCLIShape(t *testing.T) {
200 c := NewOpenAICodex("token", "acct", "https://example.test/backend-api/codex/responses").(*codexClient)206 c := NewOpenAICodex("token", "acct", "https://example.test/backend-api/codex/responses").(*codexClient)
201 var gotReq *http.Request207 var gotReq *http.Request
202 var body bytes.Buffer208 var body bytes.Buffer
@@ -223,13 +229,16 @@ func TestCodexSolKeepsZotShape(t *testing.T) {
223 if gotReq == nil {229 if gotReq == nil {
224 t.Fatal("request was not sent")230 t.Fatal("request was not sent")
225 }231 }
226- if gotReq.Header.Get("originator") != "zot" {232+ if gotReq.Header.Get("originator") != "codex_cli_rs" {
227 t.Fatalf("originator = %q", gotReq.Header.Get("originator"))233 t.Fatalf("originator = %q", gotReq.Header.Get("originator"))
228 }234 }
229- if gotReq.Header.Get("session-id") != "" {235+ if gotReq.Header.Get("user-agent") != "codex_cli_rs/0.0.0" {
230- t.Fatalf("session-id = %q", gotReq.Header.Get("session-id"))236+ t.Fatalf("user-agent = %q", gotReq.Header.Get("user-agent"))
237+ }
238+ if gotReq.Header.Get("session-id") == "" {
239+ t.Fatal("session-id header missing")
231 }240 }
232- if strings.Contains(body.String(), "prompt_cache_key") {241+ if !strings.Contains(body.String(), "prompt_cache_key") {
233- t.Fatalf("Sol request unexpectedly included prompt_cache_key: %s", body.String())242+ t.Fatalf("request missing prompt_cache_key: %s", body.String())
234 }243 }
235 }244 }

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 1

Tags 477

↑↓ moveenter openesc close