{"record":{"id":"3b089ac16001814d","repo":"github/github-mcp-server","slug":"exchanging-authorization-code-w","errorCode":null,"errorMessage":"exchanging authorization code: %w","messagePattern":"exchanging authorization code: %w","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/oauth/flow.go","lineNumber":105,"sourceCode":"\t\t// Inside a container the callback binds all interfaces so the published\n\t\t// port is reachable, which also exposes it to the container network.\n\t\t// Publishing to loopback only (e.g. -p 127.0.0.1:%d:%d) keeps the\n\t\t// authorization code off the network.\n\t\tm.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))\n\t}\n\tcs := newCallbackServer(listener, state)\n\n\toc := m.oauth2Config(cs.redirect)\n\tauthURL := oc.AuthCodeURL(state, oauth2.S256ChallengeOption(verifier))\n\n\trun := func(ctx context.Context) (*oauth2.Token, error) {\n\t\tcode, err := cs.wait(ctx)\n\t\tif err != nil {\n\t\t\treturn nil, err\n\t\t}\n\t\ttok, err := oc.Exchange(ctx, code, oauth2.VerifierOption(verifier))\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"exchanging authorization code: %w\", err)\n\t\t}\n\t\treturn tok, nil\n\t}\n\n\tbrowserErr := m.openURL(authURL)\n\tswitch {\n\tcase browserErr == nil:\n\t\tm.logger.Info(\"opened browser for GitHub authorization\")\n\t\treturn &flowPlan{run: run}, nil\n\tcase errors.Is(browserErr, errNoDisplay) && m.config.CallbackPort == 0:\n\t\t// Headless host with a random callback port: every PKCE channel ends in a\n\t\t// redirect to this machine's localhost, which a browser on another machine\n\t\t// (e.g. a remote SSH client) cannot reach — so even URL elicitation would\n\t\t// dead-end. Device flow is the only channel reachable from elsewhere, so\n\t\t// prefer it when the app supports it; otherwise fall through to the manual\n\t\t// authorization URL below for a same-machine browser.\n\t\tplan, deviceErr := m.beginDevice(prompter)\n\t\tif deviceErr == nil {","sourceCodeStart":87,"sourceCodeEnd":123,"githubUrl":"https://github.com/github/github-mcp-server/blob/0ea1f775a7c73eff1bd2e25904d01136756bbfe2/internal/oauth/flow.go#L87-L123","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"exchanging authorization code\") {\n    if strings.Contains(err.Error(), \"bad_verification_code\") {\n        // expired/used code — restart the flow, complete promptly in one tab\n    }\n}","preventionTips":["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"],"tags":["oauth","pkce","token-exchange","configuration","user-action"],"backgroundTag":null,"analyzedSha":"0ea1f775a7c73eff1bd2e25904d01136756bbfe2","analyzedAt":"2026-08-15T18:10:19.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}