charmbracelet/crush · error
${ErrorDescription}
Error message
${ErrorDescription} What it means
During the Hyper OAuth device-code flow, PollForToken receives an error field in the token response other than 'authorization_pending'. The server-supplied ErrorDescription is returned verbatim as a Go error, so the message content comes from the OAuth server.
Source
Thrown at internal/oauth/hyper/device.go:112
for {
select {
case <-ctx.Done():
return "", ctx.Err()
case <-ticker.C:
result, err := pollOnce(ctx, deviceCode)
if err != nil {
return "", err
}
if result.RefreshToken != "" {
event.Alias(result.UserID)
return result.RefreshToken, nil
}
switch result.Error {
case "authorization_pending":
continue
default:
return "", errors.New(result.ErrorDescription)
}
}
}
}
func pollOnce(ctx context.Context, deviceCode string) (TokenResponse, error) {
var result TokenResponse
url := fmt.Sprintf("%s/device/auth/%s", hyper.BaseURL(), deviceCode)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return result, fmt.Errorf("create request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", "crush")
client := &http.Client{Timeout: 30 * time.Second}
resp, err := client.Do(req)View on GitHub (pinned to 7944b8e522)
Solutions
- Restart the device-code flow (call BeginDeviceAuth / request a fresh device code) since the current code is no longer pending.
- Show the returned ErrorDescription to the user so they know what the authorization server rejected.
- Handle well-known codes (slow_down, expired_token, access_denied) explicitly before treating the error as fatal.
- Retry with a shorter polling interval if the error was slow_down.
Example fix
// before
res, err := flow.PollForToken(ctx, code)
if err != nil { return err }
// after
res, err := flow.PollForToken(ctx, code)
if err != nil {
if strings.Contains(err.Error(), "expired_token") {
return restartDeviceFlow(ctx)
}
return fmt.Errorf("device authorization failed: %w", err)
} Defensive patterns
Strategy: retry
Try / catch
token, err := flow.PollForToken(ctx, code)
if err != nil {
switch {
case strings.Contains(err.Error(), "slow_down"):
// back off and retry
case strings.Contains(err.Error(), "expired_token"):
return restartDeviceFlow(ctx)
default:
return err
}
} Prevention
- Respect the server's polling interval to avoid slow_down.
- Complete authorization within the device code's ExpiresIn window.
- Handle access_denied explicitly with a clear user message.
- Wrap PollForToken with a bounded retry that restarts the flow on terminal errors.
When it happens
Trigger: Polling the token endpoint while the device grant is in a terminal error state: the user denied the request, the device code expired (expired_token), or the server returned any unrecognized error code.
Common situations: User clicks 'cancel' on the GitHub-style authorization page and then the poller gets 'access_denied'; long waits past ExpiresIn yield 'expired_token'; network proxies inject unexpected error codes.
Related errors
- create request: %w
- device code request failed: %s - %s
- authorization timed out
- authorization failed: %s
- github copilot not available
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/3f754298e671c43c.
Report an issue: GitHub.