{"record":{"id":"856300f4da14a2f9","repo":"github/github-mcp-server","slug":"awaiting-device-authorization-w","errorCode":null,"errorMessage":"awaiting device authorization: %w","messagePattern":"awaiting device authorization: %w","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/oauth/flow.go","lineNumber":172,"sourceCode":"}\n\n// beginDevice prepares the device authorization flow. It requests a device code\n// up front (so the code can be displayed) and selects a display channel:\n// URL elicitation, then form elicitation, then a tool-response message.\nfunc (m *Manager) beginDevice(prompter Prompter) (*flowPlan, error) {\n\toc := m.oauth2Config(\"\")\n\n\tctx, cancel := context.WithTimeout(context.Background(), deviceAuthTimeout)\n\tdefer cancel()\n\tda, err := oc.DeviceAuth(ctx)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"requesting device code: %w\", err)\n\t}\n\n\trun := func(ctx context.Context) (*oauth2.Token, error) {\n\t\ttok, err := oc.DeviceAccessToken(ctx, da)\n\t\tif err != nil {\n\t\t\treturn nil, fmt.Errorf(\"awaiting device authorization: %w\", err)\n\t\t}\n\t\treturn tok, nil\n\t}\n\n\t// As with PKCE, the manual instructions double as the runtime fallback, so\n\t// build them once and reuse for both display plans and the last resort.\n\tmanual := &UserAction{\n\t\tURL:      da.VerificationURI,\n\t\tUserCode: da.UserCode,\n\t\tMessage: fmt.Sprintf(\n\t\t\t\"%s\\n\\nAfter authorizing, retry your request.\\n\\n%s\",\n\t\t\tdeviceInstruction(da), securityAdvisory,\n\t\t),\n\t}\n\n\tif canPromptURL(prompter) {\n\t\tdisplay := func(ctx context.Context) error {\n\t\t\treturn prompter.PromptURL(ctx, Prompt{","sourceCodeStart":154,"sourceCodeEnd":190,"githubUrl":"https://github.com/github/github-mcp-server/blob/0ea1f775a7c73eff1bd2e25904d01136756bbfe2/internal/oauth/flow.go#L154-L190","documentation":"The device-flow polling loop (oc.DeviceAccessToken) failed to obtain a token: the user denied the code, the code or device session expired (GitHub device codes live ~15 minutes), polling hit an unrecoverable error, or the waiting context was canceled. This runs inside flowPlan.run, so it fails the background flow after the user has been shown the code. The oauth2 library maps the RFC 8628 error responses (access_denied, expired_token, slow_down) and only the terminal ones surface here.","triggerScenarios":"oc.DeviceAccessToken at internal/oauth/flow.go:170 returns an error when: the user clicks 'Deny' at github.com/login/device (access_denied); the user_code expires before authorization completes (expired_token — typical when the code was displayed but nobody acted for ~15 min); the parent context is canceled (client disconnected, server shutting down); the token endpoint becomes unreachable mid-poll.","commonSituations":"User never visits the verification URI because the prompt got lost in MCP client output; user denies the 'github-mcp-server' authorization; long-running unattended login attempt where the code expired; client cancellation when the user gives up and closes the session; network drop during the polling window.","solutions":["Retry the login and act on the code promptly — expiry and denial dominate","If the user denied, re-consent on the next attempt or use an account/org where the app is allowed","Keep the client session alive until the flow completes; cancellation aborts the poll","For persistent failures, read the wrapped oauth2 error body for the endpoint's exact message"],"exampleFix":null,"handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"if err != nil && strings.Contains(err.Error(), \"awaiting device authorization\") {\n    if strings.Contains(err.Error(), \"denied\") { /* user refusal: prompt again or stop */ }\n    if strings.Contains(err.Error(), \"expired\") { /* restart flow, act on the code faster */ }\n    if errors.Is(ctx.Err(), context.Canceled) { /* client gave up; nothing to fix */ }\n}","preventionTips":["Surface the device code immediately and prominently so it is acted on within its ~15-minute life","Keep the client session alive until the flow completes — cancellation kills the poll","Watch for slow_down handling in long polls; the oauth2 library paces automatically, so avoid wrapping it in tight retries"],"tags":["oauth","device-flow","user-action","timeout","token-exchange"],"backgroundTag":null,"analyzedSha":"0ea1f775a7c73eff1bd2e25904d01136756bbfe2","analyzedAt":"2026-08-15T18:10:19.804Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}