{"record":{"id":"6138b85a942658db","repo":"router-for-me/CLIProxyAPI","slug":"claude-oauth-tls-split-upstream-address-w","errorCode":null,"errorMessage":"claude oauth tls: split upstream address: %w","messagePattern":"claude oauth tls: split upstream address: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/auth/claude/utls_transport.go","lineNumber":220,"sourceCode":"\tvar (\n\t\tconn net.Conn\n\t\terr  error\n\t)\n\tif contextDialer, ok := t.dialer.(proxy.ContextDialer); ok {\n\t\tconn, err = contextDialer.DialContext(ctx, network, addr)\n\t} else {\n\t\tconn, err = t.dialer.Dial(network, addr)\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"claude oauth tls: dial upstream: %w\", err)\n\t}\n\n\thost, _, errSplit := net.SplitHostPort(addr)\n\tif errSplit != nil {\n\t\tif errClose := conn.Close(); errClose != nil {\n\t\t\tlog.Debugf(\"claude oauth tls: close failed connection: %v\", errClose)\n\t\t}\n\t\treturn nil, fmt.Errorf(\"claude oauth tls: split upstream address: %w\", errSplit)\n\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}","sourceCodeStart":202,"sourceCodeEnd":238,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/auth/claude/utls_transport.go#L202-L238","documentation":"After a successful dial, net.SplitHostPort could not split the remote address into host:port. This only happens when the dialer's address lacks a port or is malformed — the transport derives addr from the HTTP client, so in normal operation it is always host:port. Seeing it indicates a custom dialer, address-rewriting proxy layer, or an internal invariant break rather than an environmental problem.","triggerScenarios":"Embedding the utlsRoundTripper with a custom dialer or transport wrapper that passes addrs like \"api.anthropic.com\" (no port) or a malformed string into dialTLSContext; unit tests stubbing the dialer with synthetic addresses.","commonSituations":"Forks/SDK embedders composing their own http.Transport with a DialTLSContext that forwards non-standard addr strings; almost impossible via public config in the stock server since the stdlib always supplies host:443.","solutions":["If you embed a custom dialer/transport, ensure every addr passed through is host:port (default :443 for https).","Log the offending addr value at debug level to find which layer produced it.","Revert any local transport wrapping and use the provided transport constructor as-is."],"exampleFix":"// before (custom wrapper drops the port)\nconn, err := dialer.DialContext(ctx, \"tcp\", host)\n\n// after\nconn, err := dialer.DialContext(ctx, \"tcp\", net.JoinHostPort(host, \"443\"))","handlingStrategy":"validation","validationCode":"if _, _, err := net.SplitHostPort(addr); err != nil {\n    addr = net.JoinHostPort(addr, \"443\") // normalize before dialing\n}","typeGuard":"func isHostPort(addr string) bool { _, _, err := net.SplitHostPort(addr); return err == nil }","tryCatchPattern":null,"preventionTips":["In custom dialers/transport wrappers, always pass net.JoinHostPort(host, port) shaped addresses.","Log addr at debug level in wrapped transports to catch malformed addresses early."],"tags":["claude","oauth","tls","internal","dialer"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}