router-for-me/CLIProxyAPI · error

claude oauth tls: handshake upstream: %w

Error message

claude oauth tls: handshake upstream: %w

What it means

The uTLS connection reached the server but the TLS handshake failed — the remote (or an intercepting middlebox) rejected or broke the handshake. Because this transport deliberately mimics Claude Code's ClientHello fingerprint, mismatches between what the middlebox expects and what is sent show up here. The wrapped error names the TLS alert or I/O cause.

Source

Thrown at internal/auth/claude/utls_transport.go:239

	}
	tlsConn := tls.UClient(conn, newClaudeOAuthTLSConfig(host, t.sessionCache), tls.HelloCustom)
	if errPreset := tlsConn.ApplyPreset(claudeOAuthTLSClientHelloSpec()); errPreset != nil {
		if errClose := tlsConn.Close(); errClose != nil {
			log.Debugf("claude oauth tls: close connection after preset failure: %v", errClose)
		}
		return nil, fmt.Errorf("claude oauth tls: apply ClientHello: %w", errPreset)
	}
	handshakeCtx := ctx
	if handshakeTimeout, _ := ctx.Value(claudeRefreshHandshakeTimeoutContextKey{}).(time.Duration); handshakeTimeout > 0 {
		var cancelHandshake context.CancelFunc
		handshakeCtx, cancelHandshake = context.WithTimeout(ctx, handshakeTimeout)
		defer cancelHandshake()
	}
	if errHandshake := tlsConn.HandshakeContext(handshakeCtx); errHandshake != nil {
		if errClose := tlsConn.Close(); errClose != nil {
			log.Debugf("claude oauth tls: close connection after handshake failure: %v", errClose)
		}
		return nil, fmt.Errorf("claude oauth tls: handshake upstream: %w", errHandshake)
	}
	return httpwire.NewOrderedRequestConn(tlsConn, claudeOAuthRequestHeaderOrder), nil
}

func (t *utlsRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
	return t.transport.RoundTrip(req)
}

func (t *utlsRoundTripper) CloseIdleConnections() {
	t.transport.CloseIdleConnections()
}

func NewAnthropicHttpClient(cfg *config.SDKConfig) *http.Client {
	return &http.Client{Transport: newUtlsRoundTripper(cfg)}
}

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Read the wrapped alert: handshake_failure after ClientHello → fingerprint/filtering issue; use the same egress path that works for claude.ai in a browser (typically set HTTPS_PROXY so the proxy terminates that hop).
  2. If a handshake timeout context was set, raise it or clear it for high-latency links.
  3. Test with `curl -v --tlsv1.2 https://api.anthropic.com` from the same host to confirm a plain handshake works, then compare.
  4. Try from a different network to distinguish server-side blocking from local middleboxes.

Example fix

# before: corporate MITM breaks fingerprint handshake
unset HTTPS_PROXY

# after: route OAuth traffic through the sanctioned proxy
export HTTPS_PROXY=http://proxy.corp.example:3128
Defensive patterns

Strategy: retry

Validate before calling

// verify a plain TLS handshake to the OAuth host works from this host first
if err := probeTLSHandshake("api.anthropic.com:443", 5*time.Second); err != nil {
    return fmt.Errorf("TLS path broken (proxy/MITM?): %w", err)
}

Try / catch

resp, err := oauthClient.Do(req)
if err != nil && strings.Contains(err.Error(), "handshake upstream") {
    if isHandshakeTimeout(err) { // raise the handshake timeout context and retry once
        resp, err = doWithHandshakeTimeout(oauthClient, req, 30*time.Second)
    } else { return fmt.Errorf("middlebox or fingerprint blocking TLS: %w", err) }
}

Prevention

When it happens

Trigger: HandshakeContext fails while refreshing/logging into Claude OAuth: server sends handshake_failure/bad certificate alerts; corporate TLS-inspection proxies re-handshake with incompatible parameters; the optional claudeRefreshHandshakeTimeoutContextKey deadline expires; CAPTCHAs/block pages that reset connections instead of speaking TLS.

Common situations: Corporate MITM proxies (Zscaler, Netskope) whose TLS response confuses the fingerprint-mimicking client; Anthropic-edge WAF occasionally rejecting unusual traffic; deadlocks on captcha interstitials; the handshake timeout context firing under satellite/high-latency links.

Understand the failure class

Related errors


AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15). Data as JSON: /api/errors/f1bed6c557ffc633. Report an issue: GitHub.