router-for-me/CLIProxyAPI · error

fetch Claude OAuth %s: HTTP client is nil

Error message

fetch Claude OAuth %s: HTTP client is nil

What it means

Returned by ClaudeAuth.fetchOAuthControlPlaneJSON when the method is invoked on a nil *ClaudeAuth receiver or when the struct's httpClient field is nil. Every OAuth control-plane GET (profile at api.anthropic.com/api/oauth/profile, roles) funnels through this helper, so a broken construction path disables all of them. ClaudeAuth is expected to be built by its constructor which always sets httpClient, so in practice this indicates the struct was created as &ClaudeAuth{} or the receiver was nil at the call site.

Source

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

}

func applyClaudeOAuthAxiosHeaders(req *http.Request) {
	if req == nil {
		return
	}
	req.Header.Set("Accept", "application/json, text/plain, */*")
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("User-Agent", "axios/1.15.2")
	req.Header.Set("Accept-Encoding", "gzip, compress, deflate, br")
	req.Header.Set("Connection", "close")
	req.Close = true
}

// fetchOAuthControlPlaneJSON issues an Axios-shaped OAuth control-plane GET and
// returns the decoded response body. label names the endpoint in error text.
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() {

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Construct ClaudeAuth only through its constructor (NewClaudeAuth or equivalent) so httpClient is always initialized
  2. If a struct literal is required, set the httpClient field explicitly: &ClaudeAuth{httpClient: &http.Client{}}
  3. Check the label in the message ('profile' vs 'roles') to find which call path has the nil receiver

Example fix

// before
auth := &ClaudeAuth{} // httpClient is nil
profile, err := auth.FetchOAuthProfile(ctx, token)

// after
auth := NewClaudeAuth(cfg) // httpClient initialized
profile, err := auth.FetchOAuthProfile(ctx, token)
Defensive patterns

Strategy: type-guard

Validate before calling

if auth == nil {
    return fmt.Errorf("ClaudeAuth is nil; construct via NewClaudeAuth")
}

Type guard

func claudeAuthReady(a *ClaudeAuth) bool {
    return a != nil && a.httpClient != nil
}

Try / catch

if !claudeAuthReady(auth) { return fmt.Errorf("auth not initialized") }
profile, err := auth.FetchOAuthProfile(ctx, token)
if err != nil && strings.Contains(err.Error(), "HTTP client is nil") {
    log.Errorf("programming error: ClaudeAuth built without constructor")
}

Prevention

When it happens

Trigger: Calling FetchOAuthProfile/FetchOAuthRoles on a ClaudeAuth built via struct literal without httpClient; invoking the method through a nil *ClaudeAuth pointer (Go allows the call, the guard catches it).

Common situations: Tests constructing ClaudeAuth{} directly instead of the constructor; refactors that store the auth in an interface whose nil concrete type is then dereferenced.

Related errors


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