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

  1. Verify the API key is a valid, current Hyper key and is passed unwrapped as the Bearer token.
  2. Run `curl -i -H "Authorization: Bearer $KEY" $HYPER_URL/v1/credits` to see the status and response body detail.
  3. If 404, confirm HYPER_URL points at a Hyper-compatible proxy that serves /v1/credits.
  4. If 429, back off and reduce polling frequency of the credits endpoint.
  5. 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

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


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