github/github-mcp-server · error
awaiting device authorization: %w
Error message
awaiting device authorization: %w
What it means
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.
Source
Thrown at internal/oauth/flow.go:172
}
// beginDevice prepares the device authorization flow. It requests a device code
// up front (so the code can be displayed) and selects a display channel:
// URL elicitation, then form elicitation, then a tool-response message.
func (m *Manager) beginDevice(prompter Prompter) (*flowPlan, error) {
oc := m.oauth2Config("")
ctx, cancel := context.WithTimeout(context.Background(), deviceAuthTimeout)
defer cancel()
da, err := oc.DeviceAuth(ctx)
if err != nil {
return nil, fmt.Errorf("requesting device code: %w", err)
}
run := func(ctx context.Context) (*oauth2.Token, error) {
tok, err := oc.DeviceAccessToken(ctx, da)
if err != nil {
return nil, fmt.Errorf("awaiting device authorization: %w", err)
}
return tok, nil
}
// As with PKCE, the manual instructions double as the runtime fallback, so
// build them once and reuse for both display plans and the last resort.
manual := &UserAction{
URL: da.VerificationURI,
UserCode: da.UserCode,
Message: fmt.Sprintf(
"%s\n\nAfter authorizing, retry your request.\n\n%s",
deviceInstruction(da), securityAdvisory,
),
}
if canPromptURL(prompter) {
display := func(ctx context.Context) error {
return prompter.PromptURL(ctx, Prompt{View on GitHub (pinned to 0ea1f775a7)
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
Defensive patterns
Strategy: retry
Try / catch
if err != nil && strings.Contains(err.Error(), "awaiting device authorization") {
if strings.Contains(err.Error(), "denied") { /* user refusal: prompt again or stop */ }
if strings.Contains(err.Error(), "expired") { /* restart flow, act on the code faster */ }
if errors.Is(ctx.Err(), context.Canceled) { /* client gave up; nothing to fix */ }
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- exchanging authorization code: %w
- authorization failed: %s
- state mismatch (possible CSRF)
- no authorization code in callback
- requesting device code: %w
AI-assisted analysis of github/github-mcp-server@0ea1f775a7 (2026-08-15).
Data as JSON: /api/errors/856300f4da14a2f9.
Report an issue: GitHub.