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
- Inspect the actual response body (log it before parsing) to see what GitLab returned instead of the expected token fields.
- Re-run the GitLab Duo OAuth login to get a clean token exchange.
- Check for proxy/VPN/SSL-interception that could rewrite the token endpoint response; bypass it or trust its CA.
- 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
- Log raw token-endpoint bodies (redacted) when validation fails to spot proxy/HTML interference.
- Bypass SSL-intercepting corporate proxies for gitlab.com or trust their CA.
- Re-login rather than reusing credentials after a malformed token response.
- Keep the library updated for GitLab API shape changes.
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
- Credential ${id} is not OAuth (provider=${provider}, type=${
- GitLab Duo Workflow OAuth refresh failed: ${response.status}
- Invalid GITLAB_REDIRECT_URI: ${raw}
- GITLAB_REDIRECT_URI must use http:// or https://, got: ${raw
- GITLAB_REDIRECT_URI loopback callbacks must use http://, got
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/af56988f17331c47.
Report an issue: GitHub.