Skip to content

Intermittent "stream error: INTERNAL_ERROR; received from peer" on non-fingerprinted upstreams (HTTP/2 RST_STREAM leaks mid-stream) #4011

Description

@sweetcornna

Summary

When a provider upstream is not one of the uTLS-fingerprinted hosts (api.anthropic.com, chatgpt.com) — e.g. a reseller/aggregator gateway, a self-hosted relay, or any OpenAI/Anthropic-compatible endpoint — requests intermittently fail mid-stream and the client receives a raw Go error:

API Error: stream error: stream ID 1; INTERNAL_ERROR; received from peer

This happens occasionally (only when the upstream resets a stream mid-response), so it presents as flaky "stream cut off" behavior.

Root cause

That string is the golang.org/x/net/http2 StreamError with errFromPeer ("received from peer") — i.e. a Go HTTP/2 client received a RST_STREAM(INTERNAL_ERROR) frame from its server. The only Go HTTP/2 client in the request path is CLIProxyAPI's own connection to the upstream.

The uTLS Chrome-fingerprint HTTP/2 round-tripper is gated to a hardcoded allow-list (internal/runtime/executor/helps/utls_client.go):

var utlsProtectedHosts = map[string]struct{}{
    "api.anthropic.com": {},
    "chatgpt.com":       {},
}

Every other host falls through to fallback = http.DefaultTransport, which has ForceAttemptHTTP2 = true and negotiates HTTP/2 whenever the upstream's ALPN offers h2 (most gateways behind nginx/Cloudflare do).

When such an upstream resets a stream mid-response:

  • The conductor only retries resets that occur before the first byte (empty_stream … Retryable: true). A mid-stream reset cannot be retried (bytes already forwarded to the client).
  • The abrupt RST_STREAM is surfaced through the executor's scanner.Err() path and forwarded verbatim, so the client sees the raw stream error … INTERNAL_ERROR; received from peer.

Impact

Any deployment whose claude/codex/gemini/openai-compat upstream points at a non-anthropic/chatgpt HTTPS endpoint that negotiates HTTP/2 is exposed: a flaky upstream channel leaks raw http2 framing errors to end users instead of failing gracefully.

Proposed fix

Force HTTP/1.1 on the fallback transport (the non-fingerprinted path), mirroring the existing cloneTransportWithHTTP11 used by the antigravity executor. Over HTTP/1.1 the RST_STREAM error class cannot occur; a mid-response upstream drop becomes a plain connection close (clean EOF), which composes cleanly with terminal-marker synthesis (e.g. #3997) for a graceful message_stop/[DONE] instead of a hard error. The uTLS HTTP/2 path for Anthropic/ChatGPT is intentionally left untouched to preserve its Chrome fingerprint.

PR with tests: follows.

Environment

  • CLIProxyAPI: dev
  • Upstream: third-party OpenAI/Anthropic-compatible aggregator (New API style) over HTTPS, ALPN h2
  • Downstream: nginx (TLS termination) → cliproxy over HTTP/1.1; client is Claude Code / OpenAI JS SDK

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions