{"record":{"id":"f1bed6c557ffc633","repo":"router-for-me/CLIProxyAPI","slug":"claude-oauth-tls-handshake-upstream-w","errorCode":null,"errorMessage":"claude oauth tls: handshake upstream: %w","messagePattern":"claude oauth tls: handshake upstream: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/auth/claude/utls_transport.go","lineNumber":239,"sourceCode":"\t}\n\ttlsConn := tls.UClient(conn, newClaudeOAuthTLSConfig(host, t.sessionCache), tls.HelloCustom)\n\tif errPreset := tlsConn.ApplyPreset(claudeOAuthTLSClientHelloSpec()); errPreset != nil {\n\t\tif errClose := tlsConn.Close(); errClose != nil {\n\t\t\tlog.Debugf(\"claude oauth tls: close connection after preset failure: %v\", errClose)\n\t\t}\n\t\treturn nil, fmt.Errorf(\"claude oauth tls: apply ClientHello: %w\", errPreset)\n\t}\n\thandshakeCtx := ctx\n\tif handshakeTimeout, _ := ctx.Value(claudeRefreshHandshakeTimeoutContextKey{}).(time.Duration); handshakeTimeout > 0 {\n\t\tvar cancelHandshake context.CancelFunc\n\t\thandshakeCtx, cancelHandshake = context.WithTimeout(ctx, handshakeTimeout)\n\t\tdefer cancelHandshake()\n\t}\n\tif errHandshake := tlsConn.HandshakeContext(handshakeCtx); errHandshake != nil {\n\t\tif errClose := tlsConn.Close(); errClose != nil {\n\t\t\tlog.Debugf(\"claude oauth tls: close connection after handshake failure: %v\", errClose)\n\t\t}\n\t\treturn nil, fmt.Errorf(\"claude oauth tls: handshake upstream: %w\", errHandshake)\n\t}\n\treturn httpwire.NewOrderedRequestConn(tlsConn, claudeOAuthRequestHeaderOrder), nil\n}\n\nfunc (t *utlsRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {\n\treturn t.transport.RoundTrip(req)\n}\n\nfunc (t *utlsRoundTripper) CloseIdleConnections() {\n\tt.transport.CloseIdleConnections()\n}\n\nfunc NewAnthropicHttpClient(cfg *config.SDKConfig) *http.Client {\n\treturn &http.Client{Transport: newUtlsRoundTripper(cfg)}\n}\n","sourceCodeStart":221,"sourceCodeEnd":255,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/auth/claude/utls_transport.go#L221-L255","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# before: corporate MITM breaks fingerprint handshake\nunset HTTPS_PROXY\n\n# after: route OAuth traffic through the sanctioned proxy\nexport HTTPS_PROXY=http://proxy.corp.example:3128","handlingStrategy":"retry","validationCode":"// verify a plain TLS handshake to the OAuth host works from this host first\nif err := probeTLSHandshake(\"api.anthropic.com:443\", 5*time.Second); err != nil {\n    return fmt.Errorf(\"TLS path broken (proxy/MITM?): %w\", err)\n}","typeGuard":null,"tryCatchPattern":"resp, err := oauthClient.Do(req)\nif err != nil && strings.Contains(err.Error(), \"handshake upstream\") {\n    if isHandshakeTimeout(err) { // raise the handshake timeout context and retry once\n        resp, err = doWithHandshakeTimeout(oauthClient, req, 30*time.Second)\n    } else { return fmt.Errorf(\"middlebox or fingerprint blocking TLS: %w\", err) }\n}","preventionTips":["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."],"tags":["claude","oauth","tls","handshake","proxy","firewall"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}