{"record":{"id":"2e5f92eb289fce88","repo":"decolua/9router","slug":"failed-to-get-user-info-error","errorCode":null,"errorMessage":"Failed to get user info: ${error}","messagePattern":"Failed to get user info: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/lib/oauth/services/antigravity.js","lineNumber":74,"sourceCode":"    }\n\n    return await response.json();\n  }\n\n  /**\n   * Get user info from Google\n   */\n  async getUserInfo(accessToken) {\n    const response = await fetch(`${this.config.userInfoUrl}?alt=json`, {\n      headers: {\n        Authorization: `Bearer ${accessToken}`,\n        Accept: \"application/json\",\n      },\n    });\n\n    if (!response.ok) {\n      const error = await response.text();\n      throw new Error(`Failed to get user info: ${error}`);\n    }\n\n    return await response.json();\n  }\n\n  /**\n   * Get common headers for Antigravity API calls\n   */\n  getApiHeaders(accessToken) {\n    return {\n      \"Authorization\": `Bearer ${accessToken}`,\n      \"Content-Type\": \"application/json\",\n      \"User-Agent\": this.config.loadCodeAssistUserAgent,\n    };\n  }\n\n  /**\n   * Get metadata object for loadCodeAssist / onboardUser API calls.","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/decolua/9router/blob/90b52e06ffd666b7929554211474d01588f6b1f8/src/lib/oauth/services/antigravity.js#L56-L92","documentation":"AntigravityService.getUserInfo calls the Google userinfo endpoint with the bearer access token and throws this on non-2xx, including the response body. It means the token was rejected or the userinfo endpoint failed — most often 401 from an expired or insufficiently-scoped access token.","triggerScenarios":"Access token expired (Google tokens last ~1h) and used after expiry; token lacks the userinfo.email scope; the token endpoint returned no access_token but the flow continued; Google userinfo endpoint returning 4xx/5xx; network/proxy failure.","commonSituations":"Fetching the profile long after the OAuth exchange with a never-refreshed token; scopes in ANTIGRAVITY_CONFIG reduced so the email scope is missing; corrupted token stored from a partial exchange response.","solutions":["Refresh the access token (or re-run OAuth) before calling getUserInfo — 401 means expired/invalid token","Verify config.scopes includes userinfo.email/userinfo.profile","Check the exchange response actually contained access_token before calling getUserInfo","If 5xx, retry after a short delay","Validate the stored token is a non-empty JWT-shaped string before use"],"exampleFix":"// before\nconst info = await svc.getUserInfo(accessToken);\n// after\nconst guard = (t) => typeof t === 'string' && t.split('.').length === 3;\nif (!guard(accessToken)) throw new Error('No valid access token — re-run OAuth');\nconst info = await svc.getUserInfo(accessToken); // refresh token first if >1h old","handlingStrategy":"validation","validationCode":"// pre-flight: only call userinfo with a plausible, fresh bearer token\nconst jwtShaped = (t) => typeof t === 'string' && t.split('.').length === 3;\nconst tokenAgeOk = (obtainedAt) => Date.now() - obtainedAt < 55 * 60 * 1000; // Google tokens ~1h\nif (!jwtShaped(accessToken) || !tokenAgeOk(tokenObtainedAt)) {\n  accessToken = await refreshAccessToken(refreshToken); // or re-run OAuth\n}","typeGuard":"const isUserInfoError = (e) => e instanceof Error && e.message.startsWith('Failed to get user info:');\nconst isAuthError = (e) => isUserInfoError(e) && /\\b40[13]\\b|Unauthorized|Forbidden/.test(e.message);","tryCatchPattern":"try { info = await svc.getUserInfo(accessToken); }\ncatch (e) {\n  if (isAuthError(e)) { accessToken = await refresh(); info = await svc.getUserInfo(accessToken); }\n  else throw e;\n}","preventionTips":["Refresh the access token before any call made long after the OAuth exchange","Include userinfo.email/userinfo.profile scopes in config.scopes","Confirm the exchange response contained access_token before storing/using it","Retry only on 5xx; 401/403 always require a token refresh or re-auth"],"tags":["oauth","access-token","userinfo","google"],"backgroundTag":"access-token-expired","analyzedSha":"90b52e06ffd666b7929554211474d01588f6b1f8","analyzedAt":"2026-08-30T21:05:45.952Z","schemaVersion":2},"datasetVersion":"2026-08-30T23:17:21.991Z"}