github/github-mcp-server · error
exchanging authorization code: %w
Error message
exchanging authorization code: %w
What it means
The authorization-code-for-token exchange (oc.Exchange with the PKCE verifier) failed at the GitHub token endpoint. The %w wrap keeps the oauth2 error, which embeds the HTTP status and response body. Distinct causes map to distinct statuses: 400 bad_verification_code (code expired, already used, or verifier mismatch), 401 bad client credentials, 403 redirect/callback restriction, network failure.
Source
Thrown at internal/oauth/flow.go:105
// Inside a container the callback binds all interfaces so the published
// port is reachable, which also exposes it to the container network.
// Publishing to loopback only (e.g. -p 127.0.0.1:%d:%d) keeps the
// authorization code off the network.
m.logger.Warn(fmt.Sprintf("OAuth callback is listening on all container interfaces; publish it to loopback only (e.g. -p 127.0.0.1:%d:%d) so the authorization code is not exposed on your network", m.config.CallbackPort, m.config.CallbackPort))
}
cs := newCallbackServer(listener, state)
oc := m.oauth2Config(cs.redirect)
authURL := oc.AuthCodeURL(state, oauth2.S256ChallengeOption(verifier))
run := func(ctx context.Context) (*oauth2.Token, error) {
code, err := cs.wait(ctx)
if err != nil {
return nil, err
}
tok, err := oc.Exchange(ctx, code, oauth2.VerifierOption(verifier))
if err != nil {
return nil, fmt.Errorf("exchanging authorization code: %w", err)
}
return tok, nil
}
browserErr := m.openURL(authURL)
switch {
case browserErr == nil:
m.logger.Info("opened browser for GitHub authorization")
return &flowPlan{run: run}, nil
case errors.Is(browserErr, errNoDisplay) && m.config.CallbackPort == 0:
// Headless host with a random callback port: every PKCE channel ends in a
// redirect to this machine's localhost, which a browser on another machine
// (e.g. a remote SSH client) cannot reach — so even URL elicitation would
// dead-end. Device flow is the only channel reachable from elsewhere, so
// prefer it when the app supports it; otherwise fall through to the manual
// authorization URL below for a same-machine browser.
plan, deviceErr := m.beginDevice(prompter)
if deviceErr == nil {View on GitHub (pinned to 0ea1f775a7)
Solutions
- Restart the login flow and complete authorization promptly — expired/used codes are the top cause
- Align the app's registered callback URL with http://localhost:{--oauth-callback-port}/callback
- Read the embedded status: 400 = code/verifier problem, 401 = client_id/secret wrong, network text = connectivity/proxy
- Avoid refreshing the callback page; complete the flow in one pass
Defensive patterns
Strategy: retry
Try / catch
if err != nil && strings.Contains(err.Error(), "exchanging authorization code") {
if strings.Contains(err.Error(), "bad_verification_code") {
// expired/used code — restart the flow, complete promptly in one tab
}
} Prevention
- Complete the authorization promptly after starting the flow; codes are short-lived
- Keep the OAuth app's registered callback URL in lockstep with --oauth-callback-port
- Never reload the callback URL after success
When it happens
Trigger: oc.Exchange at internal/oauth/flow.go:103 fails when: the user dwells too long before the callback so the 10-minute code expires; the code was consumed by a retry (browser re-GET of the redirect); the S256 verifier does not match because state came from one flow and code from another; GitHub's callback URL for the app differs from http://localhost:PORT/callback; the token endpoint is unreachable (network error).
Common situations: Browser reloads the callback URL after success, burning the code twice; clock-heavy flows on slow devices; the OAuth app's callback field still points at an old port; corporate proxies blocking POSTs to github.com/login/oauth/access_token; mixing up dev/prod app credentials.
Related errors
- awaiting device authorization: %w
- authentication required: set GITHUB_PERSONAL_ACCESS_TOKEN, c
- starting callback listener on %s: %w
- authorization failed: %s
- state mismatch (possible CSRF)
AI-assisted analysis of github/github-mcp-server@0ea1f775a7 (2026-08-15).
Data as JSON: /api/errors/3b089ac16001814d.
Report an issue: GitHub.