can1357/oh-my-pi · error · AIError.OAuthError
GitLab OAuth refresh failed: ${response.status} ${await resp
Error message
GitLab OAuth refresh failed: ${response.status} ${await response.text()} What it means
refreshGitLabDuoToken exchanges the stored refresh token for a new access token at https://gitlab.com/oauth/token (grant_type=refresh_token) for the GitLab Duo provider. On any non-2xx response this OAuthError is thrown, embedding the HTTP status and GitLab's error body. The stored refresh token is no longer usable.
Source
Thrown at packages/ai/src/registry/oauth/gitlab-duo.ts:206
const clientId = resolveClientId();
const options = resolveCallbackOptions();
const flow = new GitLabDuoOAuthFlow(callbacks, pkce, clientId, options);
return flow.login();
}
export async function refreshGitLabDuoToken(credentials: OAuthCredentials): Promise<OAuthCredentials> {
const response = await fetch(`${GITLAB_COM_URL}/oauth/token`, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
client_id: resolveClientId(),
grant_type: "refresh_token",
refresh_token: credentials.refresh,
}).toString(),
});
if (!response.ok) {
throw new AIError.OAuthError(`GitLab OAuth refresh failed: ${response.status} ${await response.text()}`, {
kind: "token-refresh",
provider: "gitlab-duo",
status: response.status,
});
}
clearGitLabDuoDirectAccessCache();
return mapTokenResponse(
(await response.json()) as {
access_token?: string;
refresh_token?: string;
expires_in?: number;
created_at?: number;
},
);
}
View on GitHub (pinned to 9690622007)
Solutions
- Re-run loginGitLabDuo to get a fresh access/refresh token pair and update stored credentials.
- If you changed GITLAB_CLIENT_ID, re-login so the refresh token is issued to the matching client id.
- Check GitLab Profile > Applications and Active Sessions; re-authorize if revoked.
- For 5xx statuses, retry after a delay — likely a transient GitLab outage.
- Fall back to a Personal Access Token via GITLAB_TOKEN if OAuth cannot be completed.
Example fix
// before: refresh assumes it never fails
tokens = await refreshGitLabDuoToken(tokens);
// after: detect invalid_grant and force re-login
try {
tokens = await refreshGitLabDuoToken(tokens);
} catch (err) {
if (err.kind === "token-refresh" && /invalid_grant|expired/.test(err.message)) {
tokens = await loginGitLabDuo(callbacks);
} else {
throw err;
}
} Defensive patterns
Strategy: try-catch
Validate before calling
if (!tokens?.refresh) {
throw new Error("no refresh token stored — run loginGitLabDuo first");
} Type guard
function isRefreshable(c: unknown): c is { refresh: string } {
return typeof c === "object" && c !== null && typeof (c as { refresh?: unknown }).refresh === "string" && (c as { refresh: string }).refresh.length > 0;
} Try / catch
try {
tokens = await refreshGitLabDuoToken(tokens);
} catch (err) {
const msg = String(err?.message ?? "");
if (err?.status === 400 && /invalid_grant|revoked|expired/i.test(msg)) {
tokens = await loginGitLabDuo(callbacks); // refresh token dead
} else if ((err?.status ?? 0) >= 500) {
await Bun.sleep(3000);
tokens = await refreshGitLabDuoToken(tokens);
} else {
throw err;
}
} Prevention
- Do not change GITLAB_CLIENT_ID after login — refresh tokens are bound to the client id they were issued to.
- Treat 400 invalid_grant as terminal: re-authenticate instead of retrying.
- Persist refreshed tokens immediately so a crash doesn't lose the rotated refresh token.
- Use refresh proactively before expiry (tokens are stored with a 5-minute safety margin).
When it happens
Trigger: Refresh POST returns !ok: expired/revoked refresh_token (invalid_grant), revoked application or user session, password change invalidating tokens, GITLAB_CLIENT_ID set to a value different from the one the token was issued to (invalid_client), or GitLab 5xx outage.
Common situations: Tokens idle past GitLab's refresh-token lifetime; user revoked the app in GitLab settings; GITLAB_CLIENT_ID changed after the original login so the client id no longer matches; automatic background refresh hitting a transient GitLab incident.
Related errors
- GitLab Duo Workflow OAuth refresh failed: ${response.status}
- GitLab OAuth token exchange failed: ${response.status} ${awa
- OAuth refresh did not produce a usable credential for provid
- OAuth provider "${provider}" does not support token refresh
- Antigravity credentials missing projectId
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/6a4058e8a4c375c1.
Report an issue: GitHub.