{"record":{"id":"11d88b29c1a0bc38","repo":"sipeed/picoclaw","slug":"no-access-token-in-response","errorCode":null,"errorMessage":"no access token in response","messagePattern":"no access token in response","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/auth/oauth.go","lineNumber":583,"sourceCode":"\t\treturn nil, fmt.Errorf(\"token exchange failed: %s\", string(body))\n\t}\n\n\treturn parseTokenResponse(body, provider)\n}\n\nfunc parseTokenResponse(body []byte, provider string) (*AuthCredential, error) {\n\tvar tokenResp struct {\n\t\tAccessToken  string `json:\"access_token\"`\n\t\tRefreshToken string `json:\"refresh_token\"`\n\t\tExpiresIn    int    `json:\"expires_in\"`\n\t\tIDToken      string `json:\"id_token\"`\n\t}\n\tif err := json.Unmarshal(body, &tokenResp); err != nil {\n\t\treturn nil, fmt.Errorf(\"parsing token response: %w\", err)\n\t}\n\n\tif tokenResp.AccessToken == \"\" {\n\t\treturn nil, fmt.Errorf(\"no access token in response\")\n\t}\n\n\tvar expiresAt time.Time\n\tif tokenResp.ExpiresIn > 0 {\n\t\texpiresAt = time.Now().Add(time.Duration(tokenResp.ExpiresIn) * time.Second)\n\t}\n\n\tcred := &AuthCredential{\n\t\tAccessToken:  tokenResp.AccessToken,\n\t\tRefreshToken: tokenResp.RefreshToken,\n\t\tExpiresAt:    expiresAt,\n\t\tProvider:     provider,\n\t\tAuthMethod:   \"oauth\",\n\t}\n\n\t// Recent OpenAI OAuth responses may only include chatgpt_account_id in id_token claims.\n\tif id := extractAccountID(tokenResp.IDToken); id != \"\" {\n\t\tcred.AccountID = id","sourceCodeStart":565,"sourceCodeEnd":601,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/auth/oauth.go#L565-L601","documentation":"parseTokenResponse (pkg/auth/oauth.go:583) decoded the body as JSON but access_token was missing or empty. The exchange technically 'succeeded' at the JSON level, yet there is no usable credential. Providers usually omit access_token only in error payloads or partial responses.","triggerScenarios":"Token endpoint returns 200 with an error object like {\"error\":\"...\"} or a payload containing only id_token/refresh_token; an API change renaming access_token; a proxy rewriting the response.","commonSituations":"Provider 200-status error envelopes (nonstandard but seen in the wild); Google-style flows returning only an id_token for some grant types; partially stubbed test servers; field renaming after a provider API version bump.","solutions":["Log the (redacted) body to see exactly which fields the provider returned","If it is an error envelope despite 200, treat the flow as failed and restart login; check the provider's docs for the payload shape","If the field was renamed (e.g. accessToken), update the json tag in parseTokenResponse","For id_token-only responses, decide whether validating id_token suffices for your flow"],"exampleFix":"// before\nif tokenResp.AccessToken == \"\" {\n\treturn nil, fmt.Errorf(\"no access token in response\")\n}\n\n// after (name the fields that were present)\nif tokenResp.AccessToken == \"\" {\n\treturn nil, fmt.Errorf(\"no access token in response (fields present: %s)\", strings.Join(presentFields(body), \", \"))\n}","handlingStrategy":"type-guard","validationCode":"// Confirm the key exists and is non-empty before trusting the flow\nvar probe struct {\n\tAccessToken string `json:\"access_token\"`\n}\nif err := json.Unmarshal(body, &probe); err == nil && strings.TrimSpace(probe.AccessToken) == \"\" {\n\treturn fmt.Errorf(\"provider returned no access_token\")\n}","typeGuard":"func isNoAccessTokenError(err error) bool {\n\treturn err != nil && strings.Contains(err.Error(), \"no access token in response\")\n}","tryCatchPattern":"cred, err := auth.ExchangeCodeForTokens(cfg, code, verifier, redirectURI)\nif err != nil && isNoAccessTokenError(err) {\n\t// 200-without-token usually means provider error envelope: restart login\n\treturn reloginFlow(cfg)\n}","preventionTips":["Check for error objects in 200 responses when integrating new providers","Re-login is the only sane recovery when no access token was issued","Log which fields the provider did return to speed up support","Pin provider API versions where possible"],"tags":["oauth","token","parsing","provider-contract","go"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}