charmbracelet/crush · warning

authorization timed out

Error message

authorization timed out

What it means

PollForToken polls the token endpoint until the device code expires; if the polling loop finishes without receiving a token (interval budget exhausted), it returns the sentinel "authorization timed out". The device code's expiresIn seconds elapsed while the user never completed browser authorization.

Source

Thrown at internal/oauth/copilot/oauth.go:97

		case <-ticker.C:
		}

		token, err := tryGetToken(ctx, dc.DeviceCode)
		if err == errPending {
			continue
		}
		if err == errSlowDown {
			interval += 5
			ticker.Reset(time.Duration(interval) * time.Second)
			continue
		}
		if err != nil {
			return nil, err
		}
		return token, nil
	}

	return nil, fmt.Errorf("authorization timed out")
}

var (
	errPending  = fmt.Errorf("pending")
	errSlowDown = fmt.Errorf("slow_down")
)

func tryGetToken(ctx context.Context, deviceCode string) (*oauth.Token, error) {
	data := url.Values{}
	data.Set("client_id", clientID)
	data.Set("device_code", deviceCode)
	data.Set("grant_type", "urn:ietf:params:oauth:grant-type:device_code")

	req, err := http.NewRequestWithContext(ctx, "POST", accessTokenURL, strings.NewReader(data.Encode()))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Accept", "application/json")

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Restart the login flow to get a fresh device code and complete authorization promptly
  2. Open the verification URL in the default browser immediately and enter the code before expiry
  3. Increase the effective deadline by restarting if your workflow is slow; report if expires_in is suspiciously short
  4. Automate the browser-open step so no manual URL copying delays authorization

Example fix

// before
tok, err := PollForToken(ctx, dc)
// after
tok, err := PollForToken(ctx, dc)
if err != nil && err.Error() == "authorization timed out" {
    // restart device flow for a fresh code
    dc, _ = RequestDeviceCode(ctx)
    tok, err = PollForToken(ctx, dc)
}
Defensive patterns

Strategy: try-catch

Validate before calling

if time.Since(dc.CreatedAt) > time.Duration(dc.ExpiresIn)*time.Second {
    return fmt.Errorf("device code already expired; restart login")
}

Try / catch

tok, err := PollForToken(ctx, dc)
if err != nil && err.Error() == "authorization timed out" {
    dc, _ = RequestDeviceCode(ctx)
    tok, err = PollForToken(ctx, dc)
}

Prevention

When it happens

Trigger: The loop's elapsed time exceeds the device code's expires_in value before the user completes login; each iteration returns errPending/errSlowDown until the deadline passes.

Common situations: User is away from the machine or ignores the browser prompt; user takes longer than the ~15 minute expiry; slow_down responses repeatedly lengthen the interval so fewer attempts fit before expiry.

Understand the failure class

Related errors


AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29). Data as JSON: /api/errors/08413936e6d7c739. Report an issue: GitHub.