Skip to content

stream disconnected before completion is account-scoped — switching ChatGPT accounts changes the failure rate ~7× #45925

Open

Description

@eteai

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?

  1. Use account A continuously → stream disconnected before completion occurs regularly.
  2. Switch to account B. Nothing else changes (same machine, network, proxy, app version, model). → failure rate drops substantially.
  3. 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-298b31939330
  • 237a7c72-3547-4870-bb26-e44b88f55c6b
  • e5552916-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.com requested 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/responses endpoint.

Request to maintainers: could you check the server-side state of the account whose x-codex-primary-reset-at is 1789805617?

  1. Is it routed to a different backend shard / capacity pool than the account with x-codex-primary-reset-at = 1789950507?
  2. Does it carry an account-level risk or degradation flag?
  3. Is it enrolled in an account-scoped experiment affecting the /backend-api/codex/responses streaming 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

  1. added
    bugSomething isn't working
    appIssues related to the Codex desktop app
    connectivityIssues involving networking or endpoint connectivity problems (disconnections)
    on Sep 16, 2026
  2. github-actions commented on Sep 16, 2026

    @github-actions
    Contributor

    Potential duplicates detected. Please review them and close your issue if it is a duplicate.

    Powered by Codex Action

  3. eteai commented on Sep 16, 2026

    @eteai
    Author

    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 same stream disconnected before completion, and likewise asking for it to be fixed "for my account"). That issue also reports a second, related error:

    Selected model is at capacity. Please try a different model.

    I see that same error — and it splits perfectly along the account boundary:

    Account Selected model is at capacity Successful output items Rate
    Account A (first period) 42 2,258 1.86%
    Account B 0 5,523 0.00%
    Account A (after switching back) 5 533 0.94%

    On 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:

    1. Is the account with x-codex-primary-reset-at = 1789805617 served from a different capacity pool or tier than the account with x-codex-primary-reset-at = 1789950507?
    2. Is there a capacity/quota dimension that is account-scoped rather than plan-scoped? Both accounts report identical headers (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.

  4. eteai commented on Sep 16, 2026

    @eteai
    Author

    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 capacity occurrences. 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

    Provider-local (US Pacific) window Activity volume at capacity occurrences
    09:00–18:00 (peak load) 64,161 0
    22:00–01:00 (lowest load) 60,877 38

    The 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 capacity occurrence 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:

    Account Active threads Successful items at capacity
    B (its heaviest hour) 31 973 0
    B 36 339 0
    B 22 130 0
    B 14 356 0
    B 9 637 0
    A 33 503 5

    Account 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:

    • time / provider load
    • the client's own concurrency
    • model (5 models affected)
    • reasoning_effort (high / medium / low all affected)
    • plan, tier, credits (headers byte-identical between the two accounts)

    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 = 1789805617 carries:

    1. an account-level trust, risk, or abuse flag (or a degradation applied by such a system);
    2. an account-scoped capacity quota distinct from the plan quota (both accounts are pro / premium with identical exposed headers);
    3. an account-scoped experiment or routing assignment affecting /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.

  5. eteai commented on Sep 16, 2026

    @eteai
    Author

    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 POST recorded 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)

    Elapsed Count
    0–5 s 30
    5–15 s 11
    15–30 s 5
    30–60 s 1

    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)

    Elapsed Count
    0–5 s 62 (74%)
    5–15 s 1
    15–30 s 3
    30–60 s 1
    60–120 s 3
    120–300 s 2
    300–1925 s 12 (14%)

    median 1 s, p90 1855 s, maximum 1925 s.

    The distribution is bimodal.

    What this implies

    • The at capacity errors are unambiguously immediate refusals — all under 36 s.
    • For 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.
    • The late failures (14%, 5–32 minutes) are where a genuine mid-stream cut would plausibly appear. They are a minority — and per the account comparison above, they also track the account rather than the transport.

    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 POST in 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.

  6. eteai commented on Sep 16, 2026

    @eteai
    Author

    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

    • Client: codex-cli 0.154.0-alpha.6.2 (the binary shipped inside the Codex desktop app)
    • Identical request every time: codex exec -s read-only --skip-git-repo-check -C <same dir> "Reply with exactly one word: OK"
    • Two batches of 20 requests each. Between batches only the signed-in ChatGPT account was changed — same machine, same network, same proxy, same client build, same models.
    • Each batch's account was verified afterwards from the response header 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

    Batch Account fingerprint Success Failure Duration
    A 1789805589 0 20 34–174 s
    B 1789950506–1789950511 20 0 21–126 s

    Account 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:

    ERROR codex_api::endpoint::responses_websocket:
      failed to connect to websocket: IO error: Connection reset by peer (os error 54),
      url: wss://chatgpt.com/backend-api/codex/responses
    ERROR: Reconnecting... 2/5 → 5/5
    warning: Falling back from WebSockets to HTTPS transport. request timed out
    

    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:

    Account After the HTTPS fallback
    A Selected model is at capacity. Please try a different model. → fails
    B completes normally

    So these are two independent problems and should not be conflated:

    1. WebSocket transport is being reset — affects both accounts equally, therefore environmental (proxy/network on the client side), not account-related. A client-side workaround exists (supports_websockets = false), and with it the same 20 requests complete in 13–16 s instead of 120–126 s.
    2. After falling back to HTTPS, account A is refused with at capacity while 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

    Request

    The account with x-codex-primary-reset-at = 1789805617 (same account as 1789805589, which is its current window value) is served materially worse than 1789950507 on 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.

  7. eteai commented on Sep 16, 2026

    @eteai
    Author

    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:

    Surface Result
    Codex (chatgpt.com/codex) on account A "Selected model is at capacity. Please try other models." — fails
    Regular ChatGPT chat on account A works normally — normal reply produced
    Codex on account B works normally

    This rules out several explanations at once:

    • Not a general account degradation or account-level flag — regular ChatGPT chat on the same account works fine.
    • Not a region-level restriction — again, regular chat is unaffected.
    • Not the network, proxy, or browser — both surfaces were exercised from the same browser on the same connection.
    • Not a client implementation issue — the failure now reproduces on three independent clients: the desktop app, the Codex CLI, and Codex on the web.

    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.

    Account A
      ├─ regular ChatGPT  (different backend path)          → works
      └─ Codex            (/backend-api/codex/responses)    → refused
    

    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 completion errors do carry a request ID in the message body.
    • Selected model is at capacity errors — the ones that actually characterise this account difference — do not carry any request ID.
    • Successful requests expose no server-side request ID at all. The response headers for a completed request contain only transfer-encoding, connection, server, and the x-codex-* quota headers — there is no x-request-id or equivalent.

    So for anything other than a stream disconnected error, 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?

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

    appIssues related to the Codex desktop appbugSomething isn't workingconnectivityIssues involving networking or endpoint connectivity problems (disconnections)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions