router-for-me/CLIProxyAPI · error

claude oauth tls: dial upstream: %w

Error message

claude oauth tls: dial upstream: %w

What it means

The uTLS-based OAuth transport failed at the raw TCP/proxy dial stage, before any TLS bytes flowed. dialTLSContext first connects through the configured (possibly proxy-aware) dialer; a failure here means network-level connectivity to the OAuth host (or to the configured proxy) is broken. The wrap includes the dialer's own error (DNS, refused, timeout, proxy failure).

Source

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

	roundTripper.transport = &http.Transport{
		ForceAttemptHTTP2: false,
		DialTLSContext:    roundTripper.dialTLSContext,
	}
	return roundTripper
}

func (t *utlsRoundTripper) dialTLSContext(ctx context.Context, network, addr string) (net.Conn, error) {
	var (
		conn net.Conn
		err  error
	)
	if contextDialer, ok := t.dialer.(proxy.ContextDialer); ok {
		conn, err = contextDialer.DialContext(ctx, network, addr)
	} else {
		conn, err = t.dialer.Dial(network, addr)
	}
	if err != nil {
		return nil, fmt.Errorf("claude oauth tls: dial upstream: %w", err)
	}

	host, _, errSplit := net.SplitHostPort(addr)
	if errSplit != nil {
		if errClose := conn.Close(); errClose != nil {
			log.Debugf("claude oauth tls: close failed connection: %v", errClose)
		}
		return nil, fmt.Errorf("claude oauth tls: split upstream address: %w", errSplit)
	}
	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 {

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Check the wrapped error: DNS failure → fix resolv.conf/DNS; connection refused/timeout → verify egress path to the OAuth host.
  2. Set or fix HTTPS_PROXY/HTTP_PROXY/NO_PROXY so the OAuth dialer uses the corporate proxy (and ensure the proxy allows CONNECT to the OAuth hosts).
  3. Test raw reachability: `curl -v https://api.anthropic.com` (with and without -x $HTTPS_PROXY) from the same host/container.
  4. In containers, verify DNS and egress with a simple Go or curl probe before running the login/refresh flow.

Example fix

# before
unset HTTPS_PROXY   # direct egress blocked by firewall

# after
export HTTPS_PROXY=http://proxy.corp.example:3128
export NO_PROXY=localhost,127.0.0.1
Defensive patterns

Strategy: retry

Validate before calling

conn, err := net.DialTimeout("tcp", "api.anthropic.com:443", 5*time.Second)
if err != nil { return fmt.Errorf("egress to OAuth host blocked: %w", err) }
conn.Close()

Try / catch

resp, err := oauthClient.Do(req)
if err != nil && strings.Contains(err.Error(), "dial upstream") {
    if isTransientNetErr(err) { time.Sleep(time.Second); resp, err = oauthClient.Do(req) }
    if err != nil { return fmt.Errorf("check DNS/proxy egress: %w", err) }
}

Prevention

When it happens

Trigger: The Claude OAuth HTTP client dialing api.anthropic.com/claude.ai through direct or proxy dialing when: DNS resolution fails, the network is down, a firewall drops egress, or HTTPS_PROXY/ALL_PROXY points at a dead/unreachable proxy.

Common situations: Corporate environments where egress requires a proxy but HTTPS_PROXY is unset (direct connections blocked); misconfigured proxy env vars (typo'd host, wrong port, proxy requiring auth); DNS failures in containers with broken resolv.conf; firewalls blocking the OAuth endpoints specifically.

Understand the failure class

Related errors


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