{"record":{"id":"949ba5a73a87c7fd","repo":"transloadit/uppy","slug":"missing-access-token-949ba5","errorCode":null,"errorMessage":"Missing access_token","messagePattern":"Missing access_token","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/@uppy/companion/src/server/provider/google/index.ts","lineNumber":39,"sourceCode":"}): Promise<{ accessToken: string }> {\n  return withGoogleErrorHandling(\n    'google',\n    'provider.google.token.refresh.error',\n    async () => {\n      const tokenRes = await getOauthClient()\n        .post('token', {\n          responseType: 'json',\n          form: {\n            refresh_token: theRefreshToken,\n            grant_type: 'refresh_token',\n            client_id: clientId,\n            client_secret: clientSecret,\n          },\n        })\n        .json<{ access_token?: unknown }>()\n      const accessToken = tokenRes.access_token\n      if (typeof accessToken !== 'string' || accessToken.length === 0) {\n        throw new Error('Missing access_token')\n      }\n      return { accessToken }\n    },\n  )\n}\n\nexport async function logout({\n  providerUserSession: { accessToken: token },\n}: {\n  providerUserSession: { accessToken: string }\n}): Promise<{ revoked: true }> {\n  return withGoogleErrorHandling(\n    'google',\n    'provider.google.logout.error',\n    async () => {\n      await got.post('https://accounts.google.com/o/oauth2/revoke', {\n        searchParams: { token },\n        responseType: 'json',","sourceCodeStart":21,"sourceCodeEnd":57,"githubUrl":"https://github.com/transloadit/uppy/blob/5d4dedd02a1ac0ae022c75c54aca76558f88e256/packages/@uppy/companion/src/server/provider/google/index.ts#L21-L57","documentation":"After Google's OAuth token endpoint responds to a refresh request, the response body must contain a non-empty string access_token. If the field is missing, not a string, or empty, Companion throws 'Missing access_token' wrapped in Google error handling — almost always because the refresh token itself was revoked or expired and Google returned an error payload instead.","triggerScenarios":"Calling refreshToken() with a stale/revoked refresh token; Google responding with { error: 'invalid_grant' } (no access_token field); a malformed client secret causing a non-token response body; clock/environment issues that make Google reject the request.","commonSituations":"User revoked app access in their Google account; refresh token older than 6 months (Google expires them); COMPANION_GOOGLE_KEY/SECRET mismatch; OAuth client credentials rotated without updating Companion.","solutions":["Have the user re-authenticate with Google so a fresh refresh token is stored (invalid_grant from a revoked/expired refresh token is the most common cause)","Verify COMPANION_GOOGLE_KEY and COMPANION_GOOGLE_SECRET match the current Google OAuth client","Log the full token response body (excluding secrets) to confirm the OAuth error Google returns","Check that the stored providerUserSession still contains a valid refresh_token"],"exampleFix":"// before\nconst { accessToken } = await googleProvider.refreshToken({ refreshToken, companion })\n\n// after\ntry {\n  const { accessToken } = await googleProvider.refreshToken({ refreshToken, companion })\n} catch (err) {\n  // refresh token revoked/expired -> force re-auth\n  throw new ProviderAuthError(err, 401)\n}","handlingStrategy":"try-catch","validationCode":"if (typeof refreshToken !== 'string' || refreshToken.length === 0) {\n  throw new ProviderAuthError('no refresh token', 401) // force re-login early\n}","typeGuard":"const hasRefreshToken = (s: unknown): s is { refreshToken: string } =>\n  typeof (s as { refreshToken?: unknown })?.refreshToken === 'string' && (s as { refreshToken: string }).refreshToken.length > 0","tryCatchPattern":"try { await provider.refreshToken(args) } catch (e) { if (e instanceof Error && e.message === 'Missing access_token') { await restartOAuthFlow() } throw e }","preventionTips":["Re-authenticate users proactively when refresh fails once instead of retrying","Keep OAuth client id/secret in sync with the Google console","Store refresh tokens securely and never reuse ones from revoked grants"],"tags":["google","oauth","refresh-token","invalid-grant","companion"],"backgroundTag":"oauth-refresh-token-invalid","analyzedSha":"5d4dedd02a1ac0ae022c75c54aca76558f88e256","analyzedAt":"2026-08-28T12:18:41.267Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}