router-for-me/CLIProxyAPI · error

fetch Claude OAuth %s: access token is empty

Error message

fetch Claude OAuth %s: access token is empty

What it means

Returned by ClaudeAuth.fetchOAuthControlPlaneJSON when the access token, after strings.TrimSpace, is empty. This is a precondition check before any network activity: the caller passed "" (or whitespace) for the token on a profile/roles fetch. It means upstream state (the stored credential) had no access token at the point of use — typically an empty token file, a failed refresh whose error was swallowed, or a caller passing the wrong variable.

Source

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

		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() {
		if errClose := resp.Body.Close(); errClose != nil {
			log.Errorf("failed to close Claude OAuth %s response body: %v", label, errClose)
		}
	}()

View on GitHub (pinned to 78f0c4079e)

Solutions

  1. Ensure the access token is refreshed (RefreshToken flow) before calling profile/roles fetches, and propagate refresh errors instead of ignoring them
  2. Inspect the stored Claude credential file under auths/ — if access_token is empty, delete it and re-run the Claude OAuth login
  3. Add a call-site check: skip the fetch when strings.TrimSpace(token) == "" and trigger refresh instead

Example fix

// before
profile, err := auth.FetchOAuthProfile(ctx, "") // guard fires

// after
if strings.TrimSpace(token) == "" {
    token, err = auth.RefreshToken(ctx)
    if err != nil { return err }
}
profile, err := auth.FetchOAuthProfile(ctx, token)
Defensive patterns

Strategy: validation

Validate before calling

if strings.TrimSpace(accessToken) == "" {
    return fmt.Errorf("access token empty; run token refresh before profile/roles fetch")
}

Type guard

func hasClaudeToken(token string) bool {
    return strings.TrimSpace(token) != ""
}

Try / catch

if !hasClaudeToken(token) { token, err = auth.RefreshToken(ctx); if err != nil { return err } }
profile, err := auth.FetchOAuthProfile(ctx, token)

Prevention

When it happens

Trigger: Calling FetchOAuthProfile/FetchOAuthRoles with an empty accessToken; a token store entry under auths/ whose access_token field is blank because a prior refresh failed silently.

Common situations: Corrupted or hand-edited Claude credential JSON files; refresh-token flow producing a response without access_token that was persisted anyway; code that reads the token before refresh completes.

Related errors


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