{"record":{"id":"4ec81a7ea6640ef1","repo":"JuliusBrussee/caveman","slug":"claude-usage-request-failed-with-http-d","errorCode":null,"errorMessage":"Claude usage request failed with HTTP %d","messagePattern":"Claude usage request failed with HTTP (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/internal/store/usage_import.go","lineNumber":149,"sourceCode":"\t\treturn nil, fmt.Errorf(\"Claude usage refresh needs CAVEMAN_CLAUDE_USAGE_JSON or CAVEMAN_CLAUDE_SESSION_KEY plus CAVEMAN_CLAUDE_ORG_ID\")\n\t}\n\tif strings.Contains(orgID, \"/\") || strings.Contains(orgID, \"..\") {\n\t\treturn nil, fmt.Errorf(\"invalid Claude organization id\")\n\t}\n\treq, err := http.NewRequest(\"GET\", \"https://claude.ai/api/organizations/\"+orgID+\"/usage\", nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treq.Header.Set(\"accept\", \"application/json\")\n\treq.Header.Set(\"cookie\", \"sessionKey=\"+sessionKey)\n\tclient := http.Client{Timeout: 15 * time.Second}\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode < 200 || resp.StatusCode >= 300 {\n\t\treturn nil, fmt.Errorf(\"Claude usage request failed with HTTP %d\", resp.StatusCode)\n\t}\n\treturn io.ReadAll(io.LimitReader(resp.Body, 4<<20))\n}\n\nfunc codexPaths(root string) ([]string, error) {\n\tpaths, _, err := codexPathsUntil(root, nil)\n\treturn paths, err\n}\n\n// codexPathsUntil preserves codexPaths' discovery contract while allowing the\n// first-run behavior scan to stop a huge cold tree. Other import callers pass no\n// deadline and retain their existing exhaustive walk.\nfunc codexPathsUntil(root string, expired func() bool) ([]string, bool, error) {\n\tvar paths []string\n\ttimeBoxed := false\n\tstopped := func() bool {\n\t\tif expired == nil || !expired() {\n\t\t\treturn false","sourceCodeStart":131,"sourceCodeEnd":167,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/proxy/internal/store/usage_import.go#L131-L167","documentation":"fetchClaudeUsageJSON performs a GET against claude.ai/api/organizations/<org>/usage with a 15-second client timeout and requires a 2xx response. Any non-2xx status (401/403 expired session, 404 wrong org, 429 rate limit, 5xx) is converted into this error carrying the status code. The body is capped at 4 MiB on success.","triggerScenarios":"An expired or invalidated sessionKey cookie (401/403); a correct-format but wrong org id (404); hammering the endpoint from automation (429); claude.ai incident or auth flow change (5xx/redirect).","commonSituations":"The browser session was logged out or rotated since the key was exported; the org id belongs to a different account; a cron job refreshing too frequently; Anthropic changes cookie handling so the copied sessionKey no longer authenticates.","solutions":["Map the code: 401/403 -> re-export a fresh CAVEMAN_CLAUDE_SESSION_KEY from a logged-in browser; 404 -> fix CAVEMAN_CLAUDE_ORG_ID; 429 -> back off the refresh cadence; 5xx -> retry later.","Prefer pinning the payload once via CAVEMAN_CLAUDE_USAGE_JSON to avoid depending on live cookie auth.","Wrap the refresh in error handling that tolerates transient failures instead of crashing the surrounding job."],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// Pre-flight is limited to config; you cannot validate a remote status locally.\n// Ensure auth inputs are well-formed before the call:\nif os.Getenv(\"CAVEMAN_CLAUDE_SESSION_KEY\") == \"\" || os.Getenv(\"CAVEMAN_CLAUDE_ORG_ID\") == \"\" {\n    return errors.New(\"claude usage credentials unset; live fetch will fail\")\n}","typeGuard":null,"tryCatchPattern":"summary, err := s.RefreshClaudeUsageFromEnv()\nif err != nil && strings.Contains(err.Error(), \"HTTP \") {\n    var code int\n    fmt.Sscanf(err.Error(), \"Claude usage request failed with HTTP %d\", &code)\n    switch {\n    case code == 401 || code == 403:\n        return errors.New(\"session key expired: re-export CAVEMAN_CLAUDE_SESSION_KEY\")\n    case code == 429:\n        time.Sleep(30 * time.Second) // then one retry, not a loop\n        summary, err = s.RefreshClaudeUsageFromEnv()\n    case code >= 500:\n        return fmt.Errorf(\"claude.ai upstream issue (HTTP %d); retry later\", code)\n    }\n}\nif err != nil {\n    return err\n}","preventionTips":["Treat 401/403 as credential rotation events, not retries.","Rate-limit scheduled refreshes to avoid 429.","Keep a pinned CAVEMAN_CLAUDE_USAGE_JSON fallback for outages."],"tags":["go","http","auth","rate-limit","usage-import","network"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}