router-for-me/CLIProxyAPI · error

fetch Claude OAuth %s: %w

Error message

fetch Claude OAuth %s: %w

What it means

Returned by ClaudeAuth.fetchOAuthControlPlaneJSON when the GET to the Anthropic OAuth control-plane endpoint fails at the transport level, wrapping the httpClient.Do error. The request is deliberately Axios-shaped (User-Agent axios/1.15.2, Accept-Encoding including br, Connection: close) to mirror Claude Code's client; intermediaries that filter on such headers, plus ordinary DNS/TLS/proxy failures, land here. A nil-body or non-2xx outcome does NOT — those are separate errors.

Source

Thrown at internal/auth/claude/anthropic_auth.go:243

func (o *ClaudeAuth) fetchOAuthControlPlaneJSON(ctx context.Context, endpoint, accessToken, label string) ([]byte, error) {
	if o == nil || o.httpClient == nil {
		return nil, fmt.Errorf("fetch Claude OAuth %s: HTTP client is nil", label)
	}
	accessToken = strings.TrimSpace(accessToken)
	if accessToken == "" {
		return nil, fmt.Errorf("fetch Claude OAuth %s: access token is empty", label)
	}
	req, errRequest := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
	if errRequest != nil {
		return nil, fmt.Errorf("create Claude OAuth %s request: %w", label, errRequest)
	}
	applyClaudeOAuthAxiosHeaders(req)
	req.Header.Set("Authorization", "Bearer "+accessToken)
	req.Header.Set("Cache-Control", "no-cache")

	resp, errDo := o.httpClient.Do(req)
	if errDo != nil {
		return nil, fmt.Errorf("fetch Claude OAuth %s: %w", label, errDo)
	}
	defer func() {
		if errClose := resp.Body.Close(); errClose != nil {
			log.Errorf("failed to close Claude OAuth %s response body: %v", label, errClose)
		}
	}()
	body, errRead := readClaudeOAuthResponseBody(resp)
	if errRead != nil {
		return nil, fmt.Errorf("read Claude OAuth %s response: %w", label, errRead)
	}
	if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices {
		return nil, fmt.Errorf("fetch Claude OAuth %s failed with status %d", label, resp.StatusCode)
	}
	return body, nil
}

// FetchOAuthProfile retrieves the account identity associated with an OAuth access token.
func (o *ClaudeAuth) FetchOAuthProfile(ctx context.Context, accessToken string) (*OAuthProfile, error) {

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Unwrap with errors.As to *url.Error and identify DNS vs dial vs TLS vs context.Canceled
  2. Test reachability with curl -v https://api.anthropic.com/api/oauth/profile from the same environment
  3. Fix proxy settings in config.yaml or bypass the intercepting middlebox
  4. Retry after checking https://status.anthropic.com for outages
Defensive patterns

Strategy: retry

Try / catch

profile, err := auth.FetchOAuthProfile(ctx, token)
if err != nil {
    var urlErr *url.Error
    if errors.As(err, &urlErr) && !errors.Is(urlErr.Err, context.Canceled) {
        profile, err = retryWithBackoff(func() (*OAuthProfile, error) {
            return auth.FetchOAuthProfile(ctx, token)
        })
    }
    if err != nil { return err }
}

Prevention

When it happens

Trigger: api.anthropic.com unreachable (DNS/firewall); TLS interception rejecting the connection; proxy misconfiguration applied via util.SetProxy; ctx cancelled during the request; an intermediary resetting connections flagged as non-browser traffic.

Common situations: Corporate proxies blocking api.anthropic.com; Great-Firewall-style regional interference; transient Anthropic outages; VPN split-tunneling excluding the API host.

Related errors


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