charmbracelet/crush · error
unexpected status code: %d
Error message
unexpected status code: %d
What it means
FetchCredits requires an exact HTTP 200 from the /v1/credits endpoint; any other status (401, 403, 404, 429, 5xx) is reported as "unexpected status code: %d". The body is not read, so the server's error detail is discarded. This signals an API-level rejection rather than a transport problem.
Source
Thrown at internal/agent/hyper/provider.go:99
ctx,
http.MethodGet,
BaseURL()+"/v1/credits",
nil,
)
if err != nil {
return nil, fmt.Errorf("could not create request: %w", err)
}
req.Header.Set("Authorization", "Bearer "+apiKey)
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to make request: %w", err)
}
defer resp.Body.Close() //nolint:errcheck
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
// Teams with hypercredit display disabled get a balance_usd field
// instead of balance, and no balance is shown for them at all.
var result struct {
Balance *int `json:"balance"`
}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
return result.Balance, nil
}
View on GitHub (pinned to 7944b8e522)
Solutions
- Verify the API key is a valid, current Hyper key and is passed unwrapped as the Bearer token.
- Run `curl -i -H "Authorization: Bearer $KEY" $HYPER_URL/v1/credits` to see the status and response body detail.
- If 404, confirm HYPER_URL points at a Hyper-compatible proxy that serves /v1/credits.
- If 429, back off and reduce polling frequency of the credits endpoint.
- If 5xx, retry later after checking Hyper's service status.
Example fix
// before
balance, err := hyper.FetchCredits(ctx, apiKey) // opaque "unexpected status code: 401"
// after
// Pre-flight: confirm credentials against the endpoint and surface the body
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, hyper.BaseURL()+"/v1/credits", nil)
req.Header.Set("Authorization", "Bearer "+apiKey)
resp, err := http.DefaultClient.Do(req)
if err != nil { return err }
b, _ := io.ReadAll(resp.Body)
resp.Body.Close()
if resp.StatusCode == http.StatusUnauthorized {
return fmt.Errorf("hyper API key rejected, refresh key: %s", b)
}
balance, err := hyper.FetchCredits(ctx, apiKey) Defensive patterns
Strategy: fallback
Validate before calling
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, hyper.BaseURL()+"/v1/credits", nil)
req.Header.Set("Authorization", "Bearer "+apiKey)
resp, err := http.DefaultClient.Do(req)
if err == nil {
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("credits endpoint unhealthy: %d", resp.StatusCode)
}
} Type guard
func isAuthFailure(err error) bool {
return strings.Contains(err.Error(), "unexpected status code: 401") ||
strings.Contains(err.Error(), "unexpected status code: 403")
} Try / catch
balance, err := hyper.FetchCredits(ctx, apiKey)
if err != nil {
var scErr string = err.Error()
if strings.Contains(scErr, "unexpected status code: 40") {
promptUserToRefreshHyperKey() // auth-class failure
} else {
log.Warn("credits unavailable, continuing without balance", "err", err)
}
return nil, nil // graceful degrade
} Prevention
- Refresh Hyper API keys before expiry and store them via secure config, not hardcoded strings.
- Treat non-200 statuses by class (4xx fatal, 429 backoff, 5xx retry-later) instead of failing hard.
- Keep a cached last-known balance (hyper.SetBalance) as a fallback display when the endpoint fails.
- Probe the endpoint once at startup to fail fast on bad keys or wrong HYPER_URL.
When it happens
Trigger: Invalid/expired API key (401/403) on the Bearer token; hitting a HYPER_URL proxy that does not implement /v1/credits (404); rate limiting (429); Hyper server error (5xx); wrong base URL path behind a reverse proxy.
Common situations: Rotated or revoked Hyper API key; pointing HYPER_URL at a service that lacks the credits route; Hyper outage returning 502/503; aggressive polling of credits causing 429s.
Related errors
- status code %d: %s
- status code %d
- unexpected status code: %d
- request failed with status code: %d
- failed to refresh MCP tools: status code %d
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/bcb1e9c70c9ab633.
Report an issue: GitHub.