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

GitLab OAuth token response missing required fields

Error message

GitLab OAuth token response missing required fields

What it means

mapTokenResponse validates the JSON body GitLab returned from the OAuth token endpoint. If access_token or refresh_token is missing/empty, or expires_in is not a number, this validation OAuthError is thrown. GitLab technically succeeded (HTTP 2xx) but the payload does not match the OAuth token-response contract the library requires.

Source

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

	const port = parsed.port ? Number.parseInt(parsed.port, 10) : parsed.protocol === "https:" ? 443 : 80;

	return {
		preferredPort: isLoopback ? port : 0,
		callbackPath: parsed.pathname || DEFAULT_CALLBACK_PATH,
		callbackHostname: isLoopback ? parsed.hostname : DEFAULT_CALLBACK_HOSTNAME,
		redirectUri: raw,
	};
}

function mapTokenResponse(payload: {
	access_token?: string;
	refresh_token?: string;
	expires_in?: number;
	created_at?: number;
}): OAuthCredentials {
	if (!payload.access_token || !payload.refresh_token || typeof payload.expires_in !== "number") {
		throw new AIError.OAuthError("GitLab OAuth token response missing required fields", {
			kind: "validation",
			provider: "gitlab-duo",
		});
	}

	const createdAtMs =
		typeof payload.created_at === "number" && Number.isFinite(payload.created_at)
			? payload.created_at * 1000
			: Date.now();

	return {
		access: payload.access_token,
		refresh: payload.refresh_token,
		expires: createdAtMs + payload.expires_in * 1000 - 5 * 60 * 1000,
	};
}

class GitLabDuoOAuthFlow extends OAuthCallbackFlow {

View on GitHub (pinned to 9690622007)

Solutions

  1. Inspect the actual response body (log it before parsing) to see what GitLab returned instead of the expected token fields.
  2. Re-run the GitLab Duo OAuth login to get a clean token exchange.
  3. Check for proxy/VPN/SSL-interception that could rewrite the token endpoint response; bypass it or trust its CA.
  4. If you are on a self-hosted GitLab, verify the instance's OAuth token endpoint behaves per GitLab's documented API.

Example fix

// before: blind cast, cryptic failure
const payload = (await response.json()) as TokenResponse;

// after: validate before handing to the flow
const payload = await response.json();
if (typeof payload.access_token !== "string" || typeof payload.expires_in !== "number") {
  console.error("unexpected token response:", payload); // inspect, then re-login
}
Defensive patterns

Strategy: type-guard

Type guard

function isGitLabTokenPayload(p: unknown): p is { access_token: string; refresh_token: string; expires_in: number; created_at?: number } {
  if (typeof p !== "object" || p === null) return false;
  const o = p as Record<string, unknown>;
  return typeof o.access_token === "string" && o.access_token.length > 0
    && typeof o.refresh_token === "string" && o.refresh_token.length > 0
    && typeof o.expires_in === "number";
}

Try / catch

try {
  tokens = await loginGitLabDuo(callbacks);
} catch (err) {
  if (err?.kind === "validation" && String(err.message).includes("missing required fields")) {
    // GitLab returned 200 with an unexpected body — check proxy/interception, then retry login
    await inspectAndReportTokenEndpointResponse();
    tokens = await loginGitLabDuo(callbacks);
  } else {
    throw err;
  }
}

Prevention

When it happens

Trigger: Called by exchangeToken (after the authorization-code exchange) or refreshGitLabDuoToken (after a refresh grant) when GitLab returns 200 with a body lacking access_token, refresh_token, or a numeric expires_in — e.g. an HTML page behind a proxy, an error JSON with 200, or a truncated response.

Common situations: Corporate proxies/interception returning HTML with 200; GitLab instance (self-hosted redirect misconfig) returning unexpected shapes; response.json() succeeding on an error envelope; changed GitLab API behavior or scope restrictions silently dropping fields.

Related errors


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