medusajs/medusa · error · MedusaError

Could not exchange token, ${r.status}, ${r.statusText}

Error message

Could not exchange token, ${r.status}, ${r.statusText}

What it means

During the Github OAuth callback, the provider exchanges the authorization code for an access token at Github's token endpoint. If Github returns a non-2xx response, the provider throws INVALID_DATA with the HTTP status and reason.

Source

Thrown at packages/modules/providers/auth-github/src/services/github.ts:119

    if (!state) {
      return { success: false, error: "No state provided, or session expired" }
    }

    const params = `client_id=${this.config_.clientId}&client_secret=${this.config_.clientSecret}&code=${code}&redirect_uri=${state.callback_url}`

    const exchangeTokenUrl = new URL(
      `https://github.com/login/oauth/access_token?${params}`
    )

    try {
      const response = await fetch(exchangeTokenUrl.toString(), {
        method: "POST",
        headers: {
          Accept: "application/json",
        },
      }).then((r) => {
        if (!r.ok) {
          throw new MedusaError(
            MedusaError.Types.INVALID_DATA,
            `Could not exchange token, ${r.status}, ${r.statusText}`
          )
        }

        return r.json()
      })

      const providerMetadata = {
        access_token: response.access_token,
        refresh_token: response.refresh_token,
        // The response is in seconds
        access_token_expires_at: new Date(
          Date.now() + response.expires_in * 1000
        ).toISOString(),
        refresh_token_expires_at: new Date(
          Date.now() + response.refresh_token_expires_in * 1000
        ).toISOString(),

View on GitHub (pinned to 5e06e544a2)

Solutions

  1. Verify clientSecret and callbackUrl in the provider options exactly match the Github OAuth app settings
  2. Retry the full OAuth flow from the beginning to get a fresh authorization code (codes are single-use and short-lived)
  3. Check Github's status page if exchanges fail consistently
Defensive patterns

Strategy: retry

Validate before calling

if (!req.query.code) {
  return res.redirect('/auth/github/github') // restart flow instead of exchanging a dead code
}

Try / catch

try { await provider.validateCallback(req.query) } catch (e) { if (e.type === 'invalid_data') res.redirect(loginUrl) else throw e } // restart flow on exchange failure

Prevention

When it happens

Trigger: The fetch to `https://github.com/login/oauth/access_token` failing: expired/reused authorization code, mismatched client secret, or mismatched redirect_uri.

Common situations: Replaying or refreshing a callback URL after the one-time code was consumed, wrong clientSecret in config, or callbackUrl differing from the OAuth app's registered callback URL.

Related errors


AI-assisted analysis of medusajs/medusa@5e06e544a2 (2026-08-27). Data as JSON: /api/errors/c65565ea7e1f1bf6. Report an issue: GitHub.