Repository navigation
stream disconnected before completion is account-scoped — switching ChatGPT accounts changes the failure rate ~7× #45925
Description
What version of the Codex App are you using (From "About Codex" dialog)?
26.908.70816 (Codex core client_version=0.154.0)
What subscription do you have?
Pro (x-codex-plan-type: pro, x-codex-active-limit: premium)
What platform are you computer?
Darwin 25.6.0 arm64 arm
What issue are you seeing?
Repeated stream disconnected before completion errors, always after an HTTP 200 OK has already been returned:
stream disconnected before completion: An error occurred while processing your request.
You can retry your request, or contact us through our help center at help.openai.com if the
error persists. Please include the request ID <id> in your message.
The failure rate is account-scoped. On the same machine, same network, same proxy, same app version and same models, the failure rate follows the ChatGPT account and not the environment:
| Account | Stream disconnects | Successful output items | Failure rate |
|---|---|---|---|
| A (first period) | 98 | 2,258 | 4.34% |
| B (second period) | 92 | 5,652 | 1.63% |
| A (after switching back) | 51 | 431 | 11.83% |
After switching back to account A the failure rate was roughly 7x that of account B.
What steps can reproduce the bug?
- Use account A continuously →
stream disconnected before completionoccurs regularly. - Switch to account B. Nothing else changes (same machine, network, proxy, app version, model). → failure rate drops substantially.
- Switch back to account A. → failures reproduce immediately.
The symptom follows the account, not the environment.
Account fingerprints (independent confirmation of the switch): the response header x-codex-primary-reset-at (the end of the 7-day billing window) acts as an account fingerprint:
- Account A (before switch):
1789805617 - Account A (after switching back):
1789805589— within 28 seconds of the above - Account B:
1789950507— about 40 hours apart from A
A's two periods share the same window boundary; B differs.
Sample request IDs (server-side identifiers — they carry the exact server-side timestamps, which is the most reliable way to correlate):
d43273fb-c928-4de6-9d2d-298b31939330237a7c72-3547-4870-bb26-e44b88f55c6be5552916-802a-45c7-a2c2-dcf282a737f5
The same 25-second window also produced request IDs 16e0017d, cc99d4ce, 99b85aed, c6d3ffc9, b7f6f277, 480812a1, 6196de1e, 44e36eb7, 41bb1583.
What is the expected behavior?
The SSE stream should run to completion, or a retryable error should be surfaced that does not require the user to identify which account is affected.
Additional information
The HTTP request itself succeeds. From the client log:
Request completed method=POST
url=https://chatgpt.com/backend-api/codex/responses
status=200 OK
server=cloudflare
cf-ray: a3bf5c40bd9d8a80-LAX
200 OK means DNS, proxy, TLS, auth and the request-body upload all succeeded. The stream then breaks after it has started. The error text is generated server-side (it carries an OpenAI request ID and points at help.openai.com).
Scale: 752 occurrences over 10 consecutive days (2026-09-06 to 2026-09-16), across 111 distinct threads.
It is neither model-specific nor effort-specific:
| Model | Count |
|---|---|
| gpt-5.6-sol | 281 |
| gpt-6-astra | 271 |
| gpt-5.6-luna | 172 |
| gpt-5.6-terra | 15 |
| gpt-5.4-mini | 10 |
reasoning_effort |
Count |
|---|---|
| high | 268 |
| medium | 225 |
| low | 179 |
Ruled out — identical or inverted between the two accounts:
| Factor | Account A | Account B |
|---|---|---|
x-codex-plan-type |
pro | pro |
x-codex-active-limit |
premium | premium |
x-codex-credits-has-credits |
False | False |
x-codex-credits-balance |
0 | 0 |
x-codex-primary-window-minutes |
10080 | 10080 |
x-codex-secondary-used-percent |
0 | 0 |
x-codex-primary-used-percent |
33 / 53 | 57 |
| Client feature flags | 60 flags | identical, 0 differences |
Note that usage percentage is not the cause: account B was more used (57%) and was markedly more stable. Client feature flags are byte-identical across both periods, so they are build-level rather than account-level.
Ruled out locally:
- Proxy (V2rayU / xray-core): 102,504 log lines mentioning OpenAI domains, 0 error lines; 25,566 dispatcher decisions, 100% routed through the proxy; 0 failures on the outbound node.
- Network:
chatgpt.comrequested 15 consecutive times, 15/15 succeeded, stable ~0.70 s; a 20 MB long-lived download completed at 1.48 MB/s. - Version regression: #9677 (0.88 SOCKS → HTTP CONNECT regression) was fixed in 0.89.0; this client is 0.154.0.
- Not related to the custom-provider variant in #41989 — this is the official
chatgpt.com/backend-api/codex/responsesendpoint.
Request to maintainers: could you check the server-side state of the account whose x-codex-primary-reset-at is 1789805617?
- Is it routed to a different backend shard / capacity pool than the account with
x-codex-primary-reset-at = 1789950507? - Does it carry an account-level risk or degradation flag?
- Is it enrolled in an account-scoped experiment affecting the
/backend-api/codex/responsesstreaming path?
Methodology caveat (stated honestly): this is an observational comparison, not a randomized trial. The two periods carried different workloads (A's first period: 2,258 successes; B: 5,652) — B handled more load yet was more stable, which strengthens rather than weakens the conclusion. The final A period is short, so 11.83% should not be treated as a stable estimate. "Disconnects" counts include automatic retry attempts, so it is not exactly equal to failed conversation turns.
Activity
github-actions commentedon Sep 16, 2026
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
eteai commentedon Sep 16, 2026
New evidence: the capacity error is exclusive to the affected account
The duplicate bot pointed at #45924 (another Pro user on the same app version
26.908.70816, reporting the samestream disconnected before completion, and likewise asking for it to be fixed "for my account"). That issue also reports a second, related error:I see that same error — and it splits perfectly along the account boundary:
Selected model is at capacityOn account B this error never occurred once across 5,523 successful output items. On account A it occurred 47 times.
The occurrences also cluster tightly around the account switches — a dense run immediately before switching away from account A, and another dense run immediately after switching back to account A. Between those two boundaries, across the entire account B period, there is not a single occurrence.
Why this matters
These two symptoms look like one root cause, and because account B produced exactly zero occurrences across 5,523 successful items, the constraint does not look like shared contention.
Additional questions for maintainers
In addition to the three questions in the original report:
x-codex-primary-reset-at = 1789805617served from a different capacity pool or tier than the account withx-codex-primary-reset-at = 1789950507?x-codex-plan-type: pro,x-codex-active-limit: premium,x-codex-credits-*identical), so the difference is not visible to the client.Related: #45924 — same app version, same primary symptom, also reported as account-specific.
eteai commentedon Sep 16, 2026
Correction: the capacity-pool explanation does not hold up — the difference is account-level and load-independent
In my previous comment I proposed that account A might simply be served by a saturated capacity pool. I have now tested that against the data and it does not hold up. Posting the correction so the investigation is not pointed the wrong way.
Two controls were run against the
Selected model is at capacityoccurrences. All times below are expressed in US Pacific, the load baseline for the provider's infrastructure.Control 1 — time of day (load) does not explain it
at capacityoccurrencesThe two windows carry a comparable volume of activity, yet the error appears only in the low-load window and never during peak hours. The client's own activity is spread across the full day (US business hours account for roughly a quarter of all activity), so this is not an artifact of the client only working at night.
If this were capacity saturation, the errors would concentrate at peak — instead they are entirely absent from it.
Breakdown of every
at capacityoccurrence by US Pacific hour: 20:00(2), 21:00(2), 22:00(7), 23:00(6), 00:00(10), 01:00(15), 02:00(2), 03:00(5), 04:00(5), 05:00(3), 07:00(1), 12:00(1).Hours 09:00, 10:00, 11:00, 13:00, 14:00, 15:00, 16:00, 17:00 and 18:00 — all of which carry substantial activity — produced zero.
Control 2 — the client's own concurrency does not explain it
Active session threads per hour, abbreviated to the relevant rows:
at capacityAccount B ran with 31 and 36 concurrent threads and recorded zero capacity errors, including its single heaviest hour (973 successful items). Account A hit 5 errors at 33 threads.
Concurrency is therefore not the trigger either — B sustained more of it with no errors at all.
What this leaves
The failure is independent of:
reasoning_effort(high / medium / low all affected)It tracks only the account. That is consistent with an account-level state rather than any load-dependent explanation.
Revised request for maintainers: rather than capacity-pool routing alone, could you check whether the account with
x-codex-primary-reset-at = 1789805617carries:pro/premiumwith identical exposed headers);/backend-api/codex/responses.Limitation, stated plainly: client-side data can establish that the difference is account-scoped and load-independent, but it cannot identify which server-side mechanism produces it. Only the provider side can distinguish a risk flag from a capacity quota.
Related: #45924 — same app version, same symptom, also reported as account-specific.
eteai commentedon Sep 16, 2026
Timing evidence: most failures are immediate, which rules out the long-connection explanation
A common explanation for this class of error is that a long-running stream is closed mid-flight by a proxy, firewall, TLS inspection, or an idle timeout. I measured time-to-failure to test whether that fits.
Method: for each error entry in the client log, locate the nearest preceding request
POSTrecorded in the same session thread and compute the elapsed time. All figures are elapsed durations, not wall-clock times.Selected model is at capacity(n = 47)median 2 s, p90 20 s, maximum 36 s.
Every occurrence failed within 36 seconds. This is an immediate refusal to start, not a stream that was cut partway through.
stream disconnected before completion(n = 84)median 1 s, p90 1855 s, maximum 1925 s.
The distribution is bimodal.
What this implies
at capacityerrors are unambiguously immediate refusals — all under 36 s.stream disconnected, the dominant mode is also immediate: 74% fail within 5 seconds. A stream that terminates in under 5 seconds was never long-running, so an idle timeout, a proxy connection ceiling, or a TLS-inspection interruption cannot account for the majority of these failures.Taken together with the earlier controls (no occurrences during provider peak load; account B sustaining 31–36 concurrent threads with zero occurrences), the picture points to a server-side decision made at request time, scoped to the account, rather than a transport-level interruption.
For completeness, the transport side was checked directly and is clean: the local proxy logged 102,504 lines mentioning the relevant domains with 0 error lines, 25,566 routing decisions 100% through the proxy, 0 outbound-node failures, and 15/15 consecutive connectivity probes succeeded at a stable ~0.70 s.
Methodology caveat: time-to-failure is estimated from the most recent request
POSTin the same thread rather than from a per-request start marker, so a small number of entries may be attributed to the wrong request. The concentration of 62 of 84 samples in the 0–5 s bucket is robust to that limitation; the individual bucket boundaries are not.eteai commentedon Sep 16, 2026
Controlled A/B experiment: same machine, same client, same prompt — only the account differs
Following the earlier comments, I ran a proper controlled experiment rather than relying on observational log analysis. This is the strongest evidence I can produce from the client side.
Method
codex-cli 0.154.0-alpha.6.2(the binary shipped inside the Codex desktop app)codex exec -s read-only --skip-git-repo-check -C <same dir> "Reply with exactly one word: OK"x-codex-primary-reset-at(billing-window fingerprint), so we know which account actually served each batch. Raw per-request output was retained for every run.Result
17898055891789950506–1789950511Account A: 100% failure. Account B: 0% failure. Same machine, same minute, same everything else.
The failure is a two-stage chain — and the WebSocket part is not the account difference
Every request in both batches went through the identical transport failure:
Account B hit exactly the same WebSocket resets on every single request and still succeeded — it fell back to HTTPS and completed normally.
The difference appears only after the fallback:
Selected model is at capacity. Please try a different model.→ failsSo these are two independent problems and should not be conflated:
supports_websockets = false), and with it the same 20 requests complete in 13–16 s instead of 120–126 s.at capacitywhile account B succeeds — this is the actual account-scoped defect, and it is downstream of the transport.This also means the "long-running WebSocket stream was closed" explanation does not account for the account difference: the WebSocket behaviour was identical on both accounts in the same session.
Corroboration
stream disconnectederror andSelected model is at capacity, and reproduced it across two different networks. The same symptom pair, from an unrelated environment.Request
The account with
x-codex-primary-reset-at = 1789805617(same account as1789805589, which is its current window value) is served materially worse than1789950507on identical inputs, on the same machine, in the same session. Could the server-side state of that account be checked for an account-level flag, quota, or routing assignment that would explain a 0/20 vs 20/20 split?Client-side data can prove the difference is account-scoped; it cannot identify which server-side mechanism produces it.
eteai commentedon Sep 16, 2026
Decisive discriminator: the failure is specific to the Codex service path, not to the account
A new control isolates the problem much more tightly than anything above.
Same account, same browser, same machine, same network, same session:
chatgpt.com/codex) on account AThis rules out several explanations at once:
What remains is narrower and more actionable: account A is limited or throttled specifically on the Codex service path, while the rest of the account is unaffected.
A note on request IDs, since it affects what can be reported
While assembling data for a separate report I found that the client is not always given a request ID:
stream disconnected before completionerrors do carry a request ID in the message body.Selected model is at capacityerrors — the ones that actually characterise this account difference — do not carry any request ID.transfer-encoding,connection,server, and thex-codex-*quota headers — there is nox-request-idor equivalent.So for anything other than a
stream disconnectederror, a client-side report cannot supply a correlating request ID. Asking users for one on a successful request or on a capacity refusal cannot be satisfied. If there is another client-visible handle that could be logged instead, that would make reports like this far easier to act on.Why this matters for triage
The failure being Codex-path-specific makes it checkable directly: is account A hitting a Codex-specific quota or rate limit, or does it carry a Codex-service-scoped flag, distinct from the account's general standing?