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
- 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).
- If a handshake timeout context was set, raise it or clear it for high-latency links.
- Test with `curl -v --tlsv1.2 https://api.anthropic.com` from the same host to confirm a plain handshake works, then compare.
- 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
- Route OAuth traffic through the sanctioned corporate proxy instead of letting MITM appliances intercept it.
- Keep the handshake timeout context generous on high-latency links.
- Compare with curl from the same host when handshake failures appear.
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
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- fetch Claude OAuth %s: %w
- claude oauth tls: dial upstream: %w
- antigravity token exchange: execute request: %w
- execute request: %w
- claude oauth tls: split upstream address: %w
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/f1bed6c557ffc633.
Report an issue: GitHub.