charmbracelet/crush · error

device code request failed: %s - %s

Error message

device code request failed: %s - %s

What it means

RequestDeviceCode calls GitHub's OAuth device-code endpoint for Copilot and expects HTTP 200. Any other status aborts with "device code request failed: <status> - <body>" so the caller can see both the HTTP status and the server's error payload. This is a network/API-level rejection before any token polling begins.

Source

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

	req, err := http.NewRequestWithContext(ctx, "POST", deviceCodeURL, strings.NewReader(data.Encode()))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Accept", "application/json")
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	req.Header.Set("User-Agent", userAgent)

	client := &http.Client{Timeout: 30 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		return nil, fmt.Errorf("device code request failed: %s - %s", resp.Status, string(body))
	}

	var dc DeviceCode
	if err := json.NewDecoder(resp.Body).Decode(&dc); err != nil {
		return nil, err
	}
	return &dc, nil
}

// PollForToken polls GitHub for the access token after user authorization.
func PollForToken(ctx context.Context, dc *DeviceCode) (*oauth.Token, error) {
	interval := max(dc.Interval, 5)
	deadline := time.Now().Add(time.Duration(dc.ExpiresIn) * time.Second)
	ticker := time.NewTicker(time.Duration(interval) * time.Second)
	defer ticker.Stop()

	for time.Now().Before(deadline) {
		select {

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Check network/proxy and retry; verify https://github.com is reachable (curl the endpoint)
  2. Inspect the response body in the error for GitHub's specific message (e.g. incorrect client_id)
  3. Check GitHub status page for outages and retry with backoff on 5xx
  4. Update Crush / the oauth client if GitHub changed the device-flow endpoint

Example fix

// before
dc, err := RequestDeviceCode(ctx)
// after
dc, err := RequestDeviceCode(ctx)
if err != nil {
    if strings.Contains(err.Error(), "50") {
        time.Sleep(2 * time.Second) // retry transient 5xx
        dc, err = RequestDeviceCode(ctx)
    }
    if err != nil { return err }
}
Defensive patterns

Strategy: retry

Validate before calling

func checkGitHubReachable() error {
    resp, err := http.Get("https://github.com")
    if err != nil { return err }
    defer resp.Body.Close()
    if resp.StatusCode >= 500 { return fmt.Errorf("github degraded: %s", resp.Status) }
    return nil
}

Try / catch

dc, err := RequestDeviceCode(ctx)
if err != nil {
    if strings.Contains(err.Error(), "device code request failed") {
        var reterr error
        for i := 0; i < 3; i++ {
            time.Sleep(time.Duration(1<<i) * time.Second)
            dc, reterr = RequestDeviceCode(ctx)
            if reterr == nil { break }
        }
        err = reterr
    }
}

Prevention

When it happens

Trigger: The POST to the GitHub device-code endpoint returns a non-200 status: 4xx (bad/missing client_id, malformed request) or 5xx (GitHub outage), or an intermediate proxy returns an error page.

Common situations: Corporate proxy/VPN intercepting requests and returning 403/502; GitHub rate limiting or incident; outdated built-in client_id after a GitHub API change; no network access (though that usually errors earlier at the transport).

Related errors


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