{"record":{"id":"031351dc76a651fd","repo":"router-for-me/CLIProxyAPI","slug":"fetch-claude-oauth-s-access-token-is-empty","errorCode":null,"errorMessage":"fetch Claude OAuth %s: access token is empty","messagePattern":"fetch Claude OAuth (.+?): access token is empty","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/auth/claude/anthropic_auth.go","lineNumber":231,"sourceCode":"\t\treturn\n\t}\n\treq.Header.Set(\"Accept\", \"application/json, text/plain, */*\")\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"User-Agent\", \"axios/1.15.2\")\n\treq.Header.Set(\"Accept-Encoding\", \"gzip, compress, deflate, br\")\n\treq.Header.Set(\"Connection\", \"close\")\n\treq.Close = true\n}\n\n// fetchOAuthControlPlaneJSON issues an Axios-shaped OAuth control-plane GET and\n// returns the decoded response body. label names the endpoint in error text.\nfunc (o *ClaudeAuth) fetchOAuthControlPlaneJSON(ctx context.Context, endpoint, accessToken, label string) ([]byte, error) {\n\tif o == nil || o.httpClient == nil {\n\t\treturn nil, fmt.Errorf(\"fetch Claude OAuth %s: HTTP client is nil\", label)\n\t}\n\taccessToken = strings.TrimSpace(accessToken)\n\tif accessToken == \"\" {\n\t\treturn nil, fmt.Errorf(\"fetch Claude OAuth %s: access token is empty\", label)\n\t}\n\treq, errRequest := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)\n\tif errRequest != nil {\n\t\treturn nil, fmt.Errorf(\"create Claude OAuth %s request: %w\", label, errRequest)\n\t}\n\tapplyClaudeOAuthAxiosHeaders(req)\n\treq.Header.Set(\"Authorization\", \"Bearer \"+accessToken)\n\treq.Header.Set(\"Cache-Control\", \"no-cache\")\n\n\tresp, errDo := o.httpClient.Do(req)\n\tif errDo != nil {\n\t\treturn nil, fmt.Errorf(\"fetch Claude OAuth %s: %w\", label, errDo)\n\t}\n\tdefer func() {\n\t\tif errClose := resp.Body.Close(); errClose != nil {\n\t\t\tlog.Errorf(\"failed to close Claude OAuth %s response body: %v\", label, errClose)\n\t\t}\n\t}()","sourceCodeStart":213,"sourceCodeEnd":249,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/auth/claude/anthropic_auth.go#L213-L249","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure the access token is refreshed (RefreshToken flow) before calling profile/roles fetches, and propagate refresh errors instead of ignoring them","Inspect the stored Claude credential file under auths/ — if access_token is empty, delete it and re-run the Claude OAuth login","Add a call-site check: skip the fetch when strings.TrimSpace(token) == \"\" and trigger refresh instead"],"exampleFix":"// before\nprofile, err := auth.FetchOAuthProfile(ctx, \"\") // guard fires\n\n// after\nif strings.TrimSpace(token) == \"\" {\n    token, err = auth.RefreshToken(ctx)\n    if err != nil { return err }\n}\nprofile, err := auth.FetchOAuthProfile(ctx, token)","handlingStrategy":"validation","validationCode":"if strings.TrimSpace(accessToken) == \"\" {\n    return fmt.Errorf(\"access token empty; run token refresh before profile/roles fetch\")\n}","typeGuard":"func hasClaudeToken(token string) bool {\n    return strings.TrimSpace(token) != \"\"\n}","tryCatchPattern":"if !hasClaudeToken(token) { token, err = auth.RefreshToken(ctx); if err != nil { return err } }\nprofile, err := auth.FetchOAuthProfile(ctx, token)","preventionTips":["Refresh tokens before every profile/roles fetch and propagate refresh errors","Re-run the Claude OAuth login when the stored credential file has an empty access_token"],"tags":["claude","oauth","validation","token"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}