decolua/9router · error · Error
Failed to get user info: ${error}
Error message
Failed to get user info: ${error} What it means
GeminiCLIService.getUserInfo() (src/lib/oauth/services/gemini.js:118) fetches the authenticated user's profile from GEMINI_CONFIG.userInfoUrl (Google's userinfo endpoint, ?alt=json) with the fresh access token. A non-2xx response is wrapped into 'Failed to get user info: <body>' and thrown, aborting connect() before tokens can be saved.
Source
Thrown at src/lib/oauth/services/gemini.js:118
}
return projectId;
}
/**
* 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();
}
/**
* Save Gemini CLI tokens to server
*/
async saveTokens(tokens, userInfo, projectId) {
const { server, token, userId } = getServerCredentials();
const response = await fetch(`${server}/api/cli/providers/gemini-cli`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
"X-User-Id": userId,
},View on GitHub (pinned to 90b52e06ff)
Solutions
- Check the embedded body/status: 401 usually means scope or token problem, 5xx means retry.
- Re-run connect() and approve all requested scopes (including profile/userinfo) on the Google consent screen.
- Verify GEMINI_CONFIG.userInfoUrl and scopes in src/lib/oauth/constants/oauth.js still match the Gemini CLI OAuth client registration.
- For 5xx, add a short backoff retry before failing.
- Check system clock skew if 401 occurs immediately after a successful token exchange.
Example fix
// before
if (!response.ok) {
const error = await response.text();
throw new Error(`Failed to get user info: ${error}`);
}
// after
if (!response.ok) {
const error = await response.text();
if (response.status >= 500) {
await new Promise((r) => setTimeout(r, 1500));
return this.getUserInfo(accessToken);
}
throw new Error(`Failed to get user info (HTTP ${response.status}): ${error}`);
} Defensive patterns
Strategy: try-catch
Validate before calling
// pre-checks before the userinfo call
if (!tokens?.access_token) throw new Error("No access token from exchange — cannot fetch user info");
// scopes granted should include profile/userinfo
const granted = String(tokens.scope || "");
if (!/profile|openid/.test(granted)) console.warn("Granted scopes lack profile/userinfo — getUserInfo may 401/403:", granted); Type guard
function isTransientHttpError(status) {
return status === 429 || status >= 500;
} Try / catch
try {
const userInfo = await geminiService.getUserInfo(tokens.access_token);
} catch (err) {
const m = /Failed to get user info: (.*)/s.exec(err.message);
if (m && /401|403/.test(m[1])) {
console.error("Token lacks userinfo scope — re-run connect() and approve all scopes on the consent screen.");
} else if (m && /429|5\d\d/.test(m[1])) {
console.error("Transient Google error — retry with backoff.");
} else throw err;
} Prevention
- Approve every requested scope on the Google consent screen, including profile.
- Call getUserInfo immediately after exchange; don't hold tokens across restarts.
- Verify GEMINI_CONFIG.userInfoUrl and scopes match the current Gemini CLI OAuth client.
- Keep clock skew near zero to avoid instant-401s on fresh tokens.
- Add bounded retries only for 429/5xx statuses.
When it happens
Trigger: getUserInfo(accessToken) called in connect() (src/lib/oauth/services/gemini.js:220) when Google returns non-2xx — 401 if the token is invalid/scope lacks userinfo.profile or openid, 403 if the OAuth client isn't allowed the userinfo scope, or 5xx transient Google errors.
Common situations: User consented but deselected profile scopes; the bundled Gemini CLI OAuth client's allowed scopes changed upstream; token endpoint returned a token but the userinfo endpoint rejects it moments later (clock skew or propagation delay); transient 5xx right after login.
Related errors
- `ClinePass token exchange failed: ${error}`
- `CodeBuddy state request failed: ${await response.text()}`
- `CodeBuddy Intl state request failed: ${await response.text(
- `Token exchange failed: ${error}`
- `Device auth initiation failed: ${error}`
AI-assisted analysis of decolua/9router@90b52e06ff (2026-08-30).
Data as JSON: /api/errors/4271ae6fee8d1cd2.
Report an issue: GitHub.