{"record":{"id":"1c78b8b80bb7828b","repo":"github/github-mcp-server","slug":"w-w","errorCode":null,"errorMessage":"%w: %w","messagePattern":"%w: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"internal/oauth/flow.go","lineNumber":84,"sourceCode":"}\n\n// beginPKCE prepares the authorization-code + PKCE flow. It binds the callback\n// server and selects the most secure available display channel: browser\n// auto-open, then URL elicitation, then a tool-response message. On a headless\n// host with a random callback port it diverts to device flow, whose redirect\n// does not depend on reaching this machine's localhost.\nfunc (m *Manager) beginPKCE(prompter Prompter) (*flowPlan, error) {\n\tstate, err := randomState()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tverifier := oauth2.GenerateVerifier()\n\n\t// Bind to all interfaces only inside a container, where the published port\n\t// is delivered via eth0 rather than loopback. Native runs stay on loopback.\n\tlistener, err := listenCallback(m.config.CallbackPort, m.inDocker())\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"%w: %w\", errCallbackBind, err)\n\t}\n\tif m.inDocker() {\n\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}","sourceCodeStart":66,"sourceCodeEnd":102,"githubUrl":"https://github.com/github/github-mcp-server/blob/0ea1f775a7c73eff1bd2e25904d01136756bbfe2/internal/oauth/flow.go#L66-L102","documentation":"beginPKCE wraps any listenCallback failure as '%w: %w' with the sentinel errCallbackBind (declared at flow.go:19 as 'OAuth callback listener could not bind'). The sentinel lets begin() distinguish genuine bind failures from unrelated preparation errors (e.g. randomState failing): only errors.Is(err, errCallbackBind) escalate to the fatal fixed-port message (115); everything else falls through to device flow. So this message itself appears in logs ('PKCE flow unavailable, falling back to device flow') when the port was random.","triggerScenarios":"listenCallback fails at internal/oauth/flow.go:82-85 — port occupied (with CallbackPort==0, the OS-chosen random port is exhausted on ephemeral-range-heavy systems), ephemeral range exhausted ('cannot assign requested address'), or bind permission denied. With a fixed port, this error is rewrapped into 115; with a random port (0) or non-Docker fallback paths, it is logged as the reason for device-flow fallback.","commonSituations":"Massively many TIME_WAIT/ephemeral sockets exhausting the local port range; container net.ipv4.ip_local_port_range too small; a random port that collided with a busy service; heavy test churn binding thousands of listeners.","solutions":["If you saw the fatal variant (115), follow its guidance; if this appears as a device-flow fallback 'reason', a random bind failed — usually transient, retry","Widen the ephemeral range / reduce TIME_WAIT pressure (net.ipv4.ip_local_port_range, tcp_tw_reuse) on socket-churn hosts","Specify a fixed high port with --oauth-callback-port to avoid ephemeral-range contention (accepting the 115 escalation if it is taken)","Simply retry the login — random-port collisions vanish on re-attempt"],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":"errors.Is(err, errCallbackBind)","tryCatchPattern":"// inside begin(): only bind failures on a fixed port escalate; everything else falls back\nif m.config.CallbackPort != 0 && errors.Is(err, errCallbackBind) { return nil, fatal }\nm.logger.Info(\"PKCE flow unavailable, falling back to device flow\", \"reason\", err)","preventionTips":["On hosts with heavy socket churn, widen ip_local_port_range or set a fixed high callback port","Retry login once on a random-port bind collision before investigating"],"tags":["oauth","port","fallback","network","internals"],"backgroundTag":null,"analyzedSha":"0ea1f775a7c73eff1bd2e25904d01136756bbfe2","analyzedAt":"2026-08-15T18:10:19.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}