can1357/oh-my-pi · error · AIError.OAuthError

GitLab OAuth token exchange failed: ${response.status} ${awa

Error message

GitLab OAuth token exchange failed: ${response.status} ${await response.text()}

What it means

exchangeToken finishes the browser OAuth flow by POSTing the authorization code + PKCE verifier to https://gitlab.com/oauth/token. If GitLab answers non-2xx, this OAuthError is thrown with the status and response body in the message. It means the one-time authorization code could not be converted into tokens.

Source

Thrown at packages/ai/src/registry/oauth/gitlab-duo.ts:164

				"Personal Access Token via GITLAB_TOKEN.",
		};
	}

	override async exchangeToken(code: string, _state: string, redirectUri: string): Promise<OAuthCredentials> {
		const response = await this.#fetch(`${GITLAB_COM_URL}/oauth/token`, {
			method: "POST",
			headers: { "Content-Type": "application/x-www-form-urlencoded" },
			body: new URLSearchParams({
				client_id: this.#clientId,
				grant_type: "authorization_code",
				code,
				code_verifier: this.#pkce.verifier,
				redirect_uri: redirectUri,
			}).toString(),
		});

		if (!response.ok) {
			throw new AIError.OAuthError(
				`GitLab OAuth token exchange failed: ${response.status} ${await response.text()}`,
				{
					kind: "token-exchange",
					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. Ensure GITLAB_REDIRECT_URI exactly matches a redirect URI registered on your GitLab OAuth application (strict string match).
  2. Restart the login flow to obtain a fresh authorization code — codes are single-use and expire within ~10 minutes.
  3. Register your own GitLab OAuth app and set both GITLAB_CLIENT_ID and GITLAB_REDIRECT_URI if the bundled credentials fail.
  4. Bypass OAuth with GITLAB_TOKEN (Personal Access Token) if browser login is not viable.
  5. Read the status/body in the message: 400 invalid_grant usually means code reuse/expiry; 401 invalid_client means wrong client id.

Example fix

// before: client id overridden, redirect not updated
GITLAB_CLIENT_ID=my-new-app-id
GITLAB_REDIRECT_URI=http://localhost:9999/cb

// after: pair must exactly match the GitLab app registration
GITLAB_CLIENT_ID=my-new-app-id
GITLAB_REDIRECT_URI=http://localhost:8080/callback
Defensive patterns

Strategy: retry

Validate before calling

const redirectUri = process.env.GITLAB_REDIRECT_URI?.trim();
const clientId = process.env.GITLAB_CLIENT_ID?.trim();
if (Boolean(clientId) !== Boolean(redirectUri)) {
  console.warn("GITLAB_CLIENT_ID and GITLAB_REDIRECT_URI should be set together and match the GitLab app registration");
}

Try / catch

let tokens: OAuthCredentials;
try {
  tokens = await loginGitLabDuo(callbacks);
} catch (err) {
  if (err?.kind === "token-exchange" && err.status === 400) {
    // code expired/consumed or redirect mismatch — restart the flow with a fresh code
    tokens = await loginGitLabDuo(callbacks);
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: POST /oauth/token with grant_type=authorization_code returns !ok: redirect_uri mismatch (GITLAB_REDIRECT_URI not exactly matching the GitLab app registration), expired or already-used authorization code, wrong PKCE code_verifier, invalid client_id, or revoked/invalidated code.

Common situations: The documented 'The redirect URI included is not valid' failure when the bundled client id's registered redirect list changed (issue #2424); retried logins reusing a consumed code; clock skew expiring the code; switching GITLAB_CLIENT_ID without updating GITLAB_REDIRECT_URI.

Related errors


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