mastra-ai/mastra · error
GitHub OAuth token exchange returned no token: ${data.error_
Error message
GitHub OAuth token exchange returned no token: ${data.error_description ?? data.error ?? 'unknown'} What it means
Thrown by the GitHub OAuth token exchange helper when GitHub's access_token endpoint responds 200 but the JSON body has no access_token. GitHub returns error/error_description fields in the body instead of a token when the authorization code, client credentials, or redirect URI is invalid. The library surfaces that error text so the developer can see why GitHub refused the exchange.
Source
Thrown at mastracode/factory/src/integrations/github/integration.ts:1127
/** Exchange an OAuth `code` for a user access token. */
async exchangeOAuthCode(code: string, redirectUri: string): Promise<string> {
const res = await fetch('https://github.com/login/oauth/access_token', {
method: 'POST',
signal: AbortSignal.timeout(GITHUB_OAUTH_TOKEN_TIMEOUT_MS),
headers: { 'content-type': 'application/json', accept: 'application/json' },
body: JSON.stringify({
client_id: this.#clientId,
client_secret: this.#clientSecret,
code,
redirect_uri: redirectUri,
}),
});
if (!res.ok) {
throw new Error(`GitHub OAuth token exchange failed: ${res.status}`);
}
const data = (await res.json()) as { access_token?: string; error?: string; error_description?: string };
if (!data.access_token) {
throw new Error(
`GitHub OAuth token exchange returned no token: ${data.error_description ?? data.error ?? 'unknown'}`,
);
}
return data.access_token;
}
/**
* The integration's HTTP surface: the `/web/github/*` + `/auth/github/*`
* Mastra `apiRoutes` (webhook handler, install/OAuth flow, project +
* worktree + session operations). The factory folds these into the server's
* `apiRoutes` when the feature is ready. Handlers operate on this instance.
*/
routes(ctx: IntegrationContext): ApiRoute[] {
this.#storage = ctx.storage;
const ingestFactoryEvent = attachGithubRules(this, ctx);
return buildGithubRoutes({
github: this,
auth: ctx.auth,View on GitHub (pinned to 75dd419e61)
Solutions
- Log the full response body from GitHub and fix the reported error_description (e.g. bad_verification_code means the code was reused or expired).
- Verify GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET, and redirect_uri exactly match the GitHub OAuth app settings.
- Generate a fresh authorization code for each exchange; never retry with the same code.
- Check the GitHub OAuth app is not suspended and has not hit rate limits.
- Re-authenticate end-to-end rather than caching exchange responses.
Example fix
// before: reusing a stored code
await exchangeGithubToken({ code: savedCode });
// after: use a fresh code per exchange and check the error body first
const data = await fetchTokenEndpoint({ code: freshCode });
if (!data.access_token) throw new Error(`OAuth failed: ${data.error_description ?? data.error}`); Defensive patterns
Strategy: try-catch
Validate before calling
// check config before exchanging
if (!process.env.GITHUB_CLIENT_ID || !process.env.GITHUB_CLIENT_SECRET) throw new Error('OAuth app credentials missing'); Type guard
function hasAccessToken(d: unknown): d is { access_token: string } {
return typeof d === 'object' && d !== null && 'access_token' in d && typeof (d as any).access_token === 'string';
} Try / catch
try {
const token = await exchangeGithubToken({ code });
} catch (e) {
// message includes GitHub's error_description; treat bad_verification_code as re-auth required
console.error('OAuth exchange failed:', (e as Error).message);
return redirect('/login'); // force a fresh code
} Prevention
- Never reuse an authorization code; always start a fresh authorize redirect.
- Keep redirect_uri identical in authorize and token steps.
- Verify OAuth app client id/secret env vars at startup.
When it happens
Trigger: Calling the OAuth token exchange with an expired or already-consumed authorization code, a code_verifier/client secret mismatch, a redirect_uri that differs from the one used in the authorize step, or GitHub rate-limiting the OAuth app (body contains error but no token).
Common situations: Users re-authorizing after the code was redeemed once; misconfigured GITHUB_CLIENT_ID/SECRET env vars; localhost callback URL not registered on the GitHub OAuth app; clock/replay issues causing single-use code reuse.
Related errors
- GitHub OAuth token exchange failed: ${res.status}
- GitHub capabilities require an app-installation connection.
- Repository access did not include a bearer token.
- GitHub subscriptions require an authenticated repository ses
- Repository access did not include a bearer token for the Fac
AI-assisted analysis of mastra-ai/mastra@75dd419e61 (2026-08-30).
Data as JSON: /api/errors/ee57fc27600fff9d.
Report an issue: GitHub.