decolua/9router · error · Error

Failed to get user info: ${error}

Error message

Failed to get user info: ${error}

What it means

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.

Source

Thrown at src/lib/oauth/services/antigravity.js:74

    }

    return await response.json();
  }

  /**
   * Get user info from Google
   */
  async getUserInfo(accessToken) {
    const response = await fetch(`${this.config.userInfoUrl}?alt=json`, {
      headers: {
        Authorization: `Bearer ${accessToken}`,
        Accept: "application/json",
      },
    });

    if (!response.ok) {
      const error = await response.text();
      throw new Error(`Failed to get user info: ${error}`);
    }

    return await response.json();
  }

  /**
   * Get common headers for Antigravity API calls
   */
  getApiHeaders(accessToken) {
    return {
      "Authorization": `Bearer ${accessToken}`,
      "Content-Type": "application/json",
      "User-Agent": this.config.loadCodeAssistUserAgent,
    };
  }

  /**
   * Get metadata object for loadCodeAssist / onboardUser API calls.

View on GitHub (pinned to 90b52e06ff)

Solutions

  1. Refresh the access token (or re-run OAuth) before calling getUserInfo — 401 means expired/invalid token
  2. Verify config.scopes includes userinfo.email/userinfo.profile
  3. Check the exchange response actually contained access_token before calling getUserInfo
  4. If 5xx, retry after a short delay
  5. Validate the stored token is a non-empty JWT-shaped string before use

Example fix

// before
const info = await svc.getUserInfo(accessToken);
// after
const guard = (t) => typeof t === 'string' && t.split('.').length === 3;
if (!guard(accessToken)) throw new Error('No valid access token — re-run OAuth');
const info = await svc.getUserInfo(accessToken); // refresh token first if >1h old
Defensive patterns

Strategy: validation

Validate before calling

// pre-flight: only call userinfo with a plausible, fresh bearer token
const jwtShaped = (t) => typeof t === 'string' && t.split('.').length === 3;
const tokenAgeOk = (obtainedAt) => Date.now() - obtainedAt < 55 * 60 * 1000; // Google tokens ~1h
if (!jwtShaped(accessToken) || !tokenAgeOk(tokenObtainedAt)) {
  accessToken = await refreshAccessToken(refreshToken); // or re-run OAuth
}

Type guard

const isUserInfoError = (e) => e instanceof Error && e.message.startsWith('Failed to get user info:');
const isAuthError = (e) => isUserInfoError(e) && /\b40[13]\b|Unauthorized|Forbidden/.test(e.message);

Try / catch

try { info = await svc.getUserInfo(accessToken); }
catch (e) {
  if (isAuthError(e)) { accessToken = await refresh(); info = await svc.getUserInfo(accessToken); }
  else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30). Data as JSON: /api/errors/2e5f92eb289fce88. Report an issue: GitHub.