Skip to content

[Bug] Streaming requests to opencode.ai hang with HTTP/2 SSE (200s timeout → 502/499, Codex stuck on 'thinking') #1668

Closed

Description

@flyinsz

Client or integration

  • Codex CLI
  • Direct HTTP/API client (curl)

Area

  • Proxy and routing
  • Streaming

Summary

Requests proxied to opencode.ai (/zen/go/v1) hang forever in streaming mode (stream: true). The proxy waits the full 200s timeout, then reports connection reset / 502 upstream_server_error / 499 client_closed_request. On the client side (Codex CLI) the UI just shows "thinking" indefinitely.

This is not a proxy/DNS/network issue on the user's machine — all direct connections work fine. It is a protocol-level incompatibility between Bun's HTTP/2 fetch and the Cloudflare edge in front of opencode.ai for SSE streaming responses.

Reproduction

Same endpoint, same payload, only the HTTP version differs:

# HTTP/1.1 + stream=true  → WORKS (SSE flows, ~3s)
curl --http1.1 -N https://opencode.ai/zen/go/v1/chat/completions \
  -H "Authorization: Bearer <key>" -H "Content-Type: application/json" \
  -d '{"model":"deepseek-v4-flash","messages":[{"role":"user","content":"hi"}],"max_tokens":5,"stream":true}'
# → HTTP 200, SSE chunks received

# HTTP/2 + stream=true   → HANGS (no bytes at all, times out)
curl --http2 -N https://opencode.ai/zen/go/v1/chat/completions \
  -H "Authorization: Bearer <key>" -H "Content-Type: application/json" \
  -d '{"model":"deepseek-v4-flash","messages":[{"role":"user","content":"hi"}],"max_tokens":5,"stream":true}'
# → 0 bytes until client timeout (20s+)

# Bun fetch + stream=true (what opencodex actually does) → HANGS
# Bun's fetch negotiates HTTP/2 by default with this origin.

Non-streaming (stream: false) works over both HTTP/1.1 and HTTP/2 (~2s).

opencodex forwards all chat completions with stream: true (confirmed via ocx debug provider logs):

[ocx:openai-chat:request] {"host":"opencode.ai","model":"deepseek-v4-flash","stream":true,...}

Version

2.15.0 (@bitkyc08/opencodex, bundled Bun 1.3.14, node_modules/bun/bin/bun.exe)

Operating system

macOS (darwin)

Provider and model

opencode.ai (opencode-go → https://opencode.ai/zen/go/v1, adapter openai-chat) / deepseek-v4-flash; Codex runtime: codex 0.147.0-alpha.6.5 (ChatGPT.app); outbound: direct connection (NO_PROXY set, no proxy vars).

Logs or error output

  • service.log: repeated [upstream-retry] connection reset (opencode.ai) — retrying (2/3)
  • usage.jsonl (recent, ~100 requests): status=502 durationMs≈200000 errorCode=upstream_server_error and status=499 closeReason=client_cancel (client abandons after ~90–160s)
  • Provider model discovery (GET /models) succeeds; only the streaming POST hangs.

Redacted configuration

{
  "provider": "opencode-go",
  "baseURL": "https://opencode.ai/zen/go/v1",
  "adapter": "openai-chat",
  "model": "deepseek-v4-flash"
}

Expected behavior

Streaming responses from opencode.ai should work over whatever HTTP version Bun negotiates. Either:

  1. opencodex should force HTTP/1.1 for SSE upstreams (Bun fetch supports protocol: "http1.1"/"h1"), or
  2. expose a config option to force HTTP/1.1 per provider, or
  3. investigate whether opencode.ai's Cloudflare edge is misbehaving with HTTP/2 SSE.

Notes

  • Verified the opencodex process connects directly to 172.65.90.20:443 (opencode.ai real IP, Cloudflare range) — no system/HTTP proxy involved. macOS system proxy (FlClash :7890) was ruled out via lsof and side-by-side tests.
  • Reproducible with both opencode-zen and opencode-go (same origin opencode.ai).
  • Worth checking if other Cloudflare-fronted SSE providers are affected by Bun's HTTP/2 fetch too.

Checks

  • I searched existing issues and documentation.
  • I removed secrets, tokens, account details, request credentials, and personal data.

Activity

  1. coderabbitai commented on Aug 14, 2026

    @coderabbitai
    Contributor
    🔗 Related PRs

    #749 - fix(responses): reject expired forward continuations [merged]
    #966 - fix(codex): keep pre-connection DNS/network failures off account health (#914) [open]
    #1006 - fix(deepseek): apply bounded JSON policy on HTTP SSE for Flash [open]
    #1487 - perf: route ChatGPT Codex upstream turns over responses_websockets [open]
    #1558 - perf: route ChatGPT Codex upstream turns over responses_websockets [merged]


    📝 Issue Planner

    Check the box below or use the @coderabbitai plan command to generate an implementation plan and prompts that you can use with your favorite coding assistant.

    • Create Plan

    🧪 Issue enrichment is currently in open beta.

    To disable automatic issue enrichment, add the following to your .coderabbit.yaml:

    issue_enrichment:
      auto_enrich:
        enabled: false

    💬 Have feedback or questions? Drop into our discord!

  2. github-actions commented on Aug 14, 2026

    @github-actions
    Contributor

    Issue reopened

    The report now contains the information required by the automated check. Thanks for updating it.

  3. added
    providerProvider adapters, OpenAI-compat presets, upstream API quirks
    proxyHTTP proxy, routing, reverse-proxy / management auth
    on Aug 15, 2026
  4. Wibias commented on Aug 16, 2026

    @Wibias
    Contributor

    Triage relationship

    Runtime roadmap parent: #1691.

    This remains a separate HTTP/2/SSE interoperability symptom because it has its own reproduction and targeted mitigation path. Current implementation work is PR #1792.

  5. lidge-jun commented on Aug 16, 2026

    @lidge-jun
    Owner

    Mitigation shipped on dev by #1792.

    Each provider can now pin the HTTP version used for its upstream requests:

    { "providers": { "opencode": { "upstreamHttpVersion": "http1.1" } } }

    Values are auto (default, Bun negotiates), http1.1/h1, or http2/h2, settable through the config file or POST/PATCH /api/providers. Setting http1.1 for the affected provider avoids the HTTP/2 SSE path where the stream stalls instead of delivering events. The option is documented in the provider configuration reference, since the whole point is that someone hitting this needs to know the knob exists.

    One contract defect was fixed before merging: the management API accepted upstreamHttpVersion: null as "clear this" and POST persisted it as submitted, while the loader schema rejected null — so a caller who took the API at its word got dropped into invalid-config recovery on the next start. POST now canonicalizes null to absent, and the loader accepts null and transforms it to undefined so any config the old path already wrote still loads. Regressions cover the POST → disk → reload round trip and a pre-existing null on disk.

    Scope, honestly stated: this is an operator-facing mitigation, not a root-cause fix for the stall itself. Whether the fault is Bun's HTTP/2 client, the provider's server, or an intermediary is still unresolved, and a long-running SSE smoke test against the affected provider on the pinned path is the remaining live confirmation. Closing the OpenCodex-side scope here — if the stall reproduces with http1.1 pinned, please reopen with that detail, since it would point somewhere other than HTTP/2 negotiation.

    Verified at the exact merge head: full Cross-platform CI green.

    The change is on dev and ships with the next release; it is not in a published version yet.

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

    bugSomething isn't workingproviderProvider adapters, OpenAI-compat presets, upstream API quirksproxyHTTP proxy, routing, reverse-proxy / management authstreamingSSE, WebSocket, terminal stream frames

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions