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

  1. Re-run loginGitLabDuo to get a fresh access/refresh token pair and update stored credentials.
  2. If you changed GITLAB_CLIENT_ID, re-login so the refresh token is issued to the matching client id.
  3. Check GitLab Profile > Applications and Active Sessions; re-authorize if revoked.
  4. For 5xx statuses, retry after a delay — likely a transient GitLab outage.
  5. 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

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


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/6a4058e8a4c375c1. Report an issue: GitHub.