{"record":{"id":"bcb1e9c70c9ab633","repo":"charmbracelet/crush","slug":"unexpected-status-code-d","errorCode":null,"errorMessage":"unexpected status code: %d","messagePattern":"unexpected status code: (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/agent/hyper/provider.go","lineNumber":99,"sourceCode":"\t\tctx,\n\t\thttp.MethodGet,\n\t\tBaseURL()+\"/v1/credits\",\n\t\tnil,\n\t)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not create request: %w\", err)\n\t}\n\treq.Header.Set(\"Authorization\", \"Bearer \"+apiKey)\n\n\tclient := &http.Client{Timeout: 10 * time.Second}\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to make request: %w\", err)\n\t}\n\tdefer resp.Body.Close() //nolint:errcheck\n\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"unexpected status code: %d\", resp.StatusCode)\n\t}\n\n\t// Teams with hypercredit display disabled get a balance_usd field\n\t// instead of balance, and no balance is shown for them at all.\n\tvar result struct {\n\t\tBalance *int `json:\"balance\"`\n\t}\n\tif err := json.NewDecoder(resp.Body).Decode(&result); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to decode response: %w\", err)\n\t}\n\n\treturn result.Balance, nil\n}\n","sourceCodeStart":81,"sourceCodeEnd":113,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/agent/hyper/provider.go#L81-L113","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nbalance, err := hyper.FetchCredits(ctx, apiKey) // opaque \"unexpected status code: 401\"\n// after\n// Pre-flight: confirm credentials against the endpoint and surface the body\nreq, _ := http.NewRequestWithContext(ctx, http.MethodGet, hyper.BaseURL()+\"/v1/credits\", nil)\nreq.Header.Set(\"Authorization\", \"Bearer \"+apiKey)\nresp, err := http.DefaultClient.Do(req)\nif err != nil { return err }\nb, _ := io.ReadAll(resp.Body)\nresp.Body.Close()\nif resp.StatusCode == http.StatusUnauthorized {\n    return fmt.Errorf(\"hyper API key rejected, refresh key: %s\", b)\n}\nbalance, err := hyper.FetchCredits(ctx, apiKey)","handlingStrategy":"fallback","validationCode":"req, _ := http.NewRequestWithContext(ctx, http.MethodGet, hyper.BaseURL()+\"/v1/credits\", nil)\nreq.Header.Set(\"Authorization\", \"Bearer \"+apiKey)\nresp, err := http.DefaultClient.Do(req)\nif err == nil {\n    io.Copy(io.Discard, resp.Body)\n    resp.Body.Close()\n    if resp.StatusCode != http.StatusOK {\n        return fmt.Errorf(\"credits endpoint unhealthy: %d\", resp.StatusCode)\n    }\n}","typeGuard":"func isAuthFailure(err error) bool {\n    return strings.Contains(err.Error(), \"unexpected status code: 401\") ||\n        strings.Contains(err.Error(), \"unexpected status code: 403\")\n}","tryCatchPattern":"balance, err := hyper.FetchCredits(ctx, apiKey)\nif err != nil {\n    var scErr string = err.Error()\n    if strings.Contains(scErr, \"unexpected status code: 40\") {\n        promptUserToRefreshHyperKey() // auth-class failure\n    } else {\n        log.Warn(\"credits unavailable, continuing without balance\", \"err\", err)\n    }\n    return nil, nil // graceful degrade\n}","preventionTips":["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."],"tags":["http","api","authentication","status-code"],"backgroundTag":"unexpected-http-status","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}