charmbracelet/crush · error

copilot token request failed: %s - %s

Error message

copilot token request failed: %s - %s

What it means

getCopilotToken exchanges the GitHub OAuth token for a short-lived Copilot API token. A 403 maps to ErrNotAvailable (plan/entitlement missing), but any other non-200 status yields "copilot token request failed: <status> - <body>" with the raw response body. Called by tryGetToken (initial login) and RefreshToken (token renewal).

Source

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

	}

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

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}

	if resp.StatusCode == http.StatusForbidden {
		return nil, ErrNotAvailable
	}
	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("copilot token request failed: %s - %s", resp.Status, string(body))
	}

	var result struct {
		Token     string `json:"token"`
		ExpiresAt int64  `json:"expires_at"`
	}
	if err := json.Unmarshal(body, &result); err != nil {
		return nil, err
	}

	copilotToken := &oauth.Token{
		AccessToken:  result.Token,
		RefreshToken: githubToken,
		ExpiresAt:    result.ExpiresAt,
	}
	copilotToken.SetExpiresIn()

	return copilotToken, nil

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Read status/body in the error: 401 means re-run the full GitHub device-flow login to get a new token
  2. Retry with backoff on 5xx — especially in RefreshToken, where a transient failure should not force re-auth
  3. Verify the Copilot subscription/entitlement is active for the account
  4. Check proxy/firewall access to the Copilot token endpoint

Example fix

// before
tok, err := RefreshToken(ctx, t)
if err != nil { return err }
// after
tok, err := RefreshToken(ctx, t)
if err != nil {
    if strings.Contains(err.Error(), "50") {
        time.Sleep(backoff)
        tok, err = RefreshToken(ctx, t)
    }
    if err != nil { return err }
}
Defensive patterns

Strategy: retry

Validate before calling

func needsRelogin(err error) bool {
    return err != nil && strings.Contains(err.Error(), "copilot token request failed: 401")
}

Try / catch

tok, err := RefreshToken(ctx, t)
if err != nil {
    if needsRelogin(err) {
        return fullDeviceFlowLogin(ctx) // 401: token dead, re-auth
    }
    if strings.Contains(err.Error(), "50") {
        time.Sleep(backoff)
        tok, err = RefreshToken(ctx, t)
    }
}

Prevention

When it happens

Trigger: The GET to the Copilot token endpoint returns e.g. 401 (GitHub token invalid/expired/revoked), 404 (endpoint moved), or 5xx (service outage). 403 is intentionally handled separately as ErrNotAvailable.

Common situations: GitHub session token revoked or expired before refresh; user's Copilot subscription lapsed (may surface as non-403 on some paths); corporate proxy blocking the endpoint; transient GitHub 5xx during RefreshToken causing spurious logout.

Related errors


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