{"record":{"id":"ee57fc27600fff9d","repo":"mastra-ai/mastra","slug":"github-oauth-token-exchange-returned-no-token-d","errorCode":null,"errorMessage":"GitHub OAuth token exchange returned no token: ${data.error_description ?? data.error ?? 'unknown'}","messagePattern":"GitHub OAuth token exchange returned no token: (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"mastracode/factory/src/integrations/github/integration.ts","lineNumber":1127,"sourceCode":"  /** Exchange an OAuth `code` for a user access token. */\n  async exchangeOAuthCode(code: string, redirectUri: string): Promise<string> {\n    const res = await fetch('https://github.com/login/oauth/access_token', {\n      method: 'POST',\n      signal: AbortSignal.timeout(GITHUB_OAUTH_TOKEN_TIMEOUT_MS),\n      headers: { 'content-type': 'application/json', accept: 'application/json' },\n      body: JSON.stringify({\n        client_id: this.#clientId,\n        client_secret: this.#clientSecret,\n        code,\n        redirect_uri: redirectUri,\n      }),\n    });\n    if (!res.ok) {\n      throw new Error(`GitHub OAuth token exchange failed: ${res.status}`);\n    }\n    const data = (await res.json()) as { access_token?: string; error?: string; error_description?: string };\n    if (!data.access_token) {\n      throw new Error(\n        `GitHub OAuth token exchange returned no token: ${data.error_description ?? data.error ?? 'unknown'}`,\n      );\n    }\n    return data.access_token;\n  }\n\n  /**\n   * The integration's HTTP surface: the `/web/github/*` + `/auth/github/*`\n   * Mastra `apiRoutes` (webhook handler, install/OAuth flow, project +\n   * worktree + session operations). The factory folds these into the server's\n   * `apiRoutes` when the feature is ready. Handlers operate on this instance.\n   */\n  routes(ctx: IntegrationContext): ApiRoute[] {\n    this.#storage = ctx.storage;\n    const ingestFactoryEvent = attachGithubRules(this, ctx);\n    return buildGithubRoutes({\n      github: this,\n      auth: ctx.auth,","sourceCodeStart":1109,"sourceCodeEnd":1145,"githubUrl":"https://github.com/mastra-ai/mastra/blob/75dd419e613fe9c39f846ffc500716141b74fda6/mastracode/factory/src/integrations/github/integration.ts#L1109-L1145","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before: reusing a stored code\nawait exchangeGithubToken({ code: savedCode });\n// after: use a fresh code per exchange and check the error body first\nconst data = await fetchTokenEndpoint({ code: freshCode });\nif (!data.access_token) throw new Error(`OAuth failed: ${data.error_description ?? data.error}`);","handlingStrategy":"try-catch","validationCode":"// check config before exchanging\nif (!process.env.GITHUB_CLIENT_ID || !process.env.GITHUB_CLIENT_SECRET) throw new Error('OAuth app credentials missing');","typeGuard":"function hasAccessToken(d: unknown): d is { access_token: string } {\n  return typeof d === 'object' && d !== null && 'access_token' in d && typeof (d as any).access_token === 'string';\n}","tryCatchPattern":"try {\n  const token = await exchangeGithubToken({ code });\n} catch (e) {\n  // message includes GitHub's error_description; treat bad_verification_code as re-auth required\n  console.error('OAuth exchange failed:', (e as Error).message);\n  return redirect('/login'); // force a fresh code\n}","preventionTips":["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."],"tags":["oauth","github","authentication"],"backgroundTag":"oauth-token-exchange-failed","analyzedSha":"75dd419e613fe9c39f846ffc500716141b74fda6","analyzedAt":"2026-08-30T00:15:31.844Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}