JuliusBrussee/caveman · error

Claude usage request failed with HTTP %d

Error message

Claude usage request failed with HTTP %d

What it means

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.

Source

Thrown at proxy/internal/store/usage_import.go:149

		return nil, fmt.Errorf("Claude usage refresh needs CAVEMAN_CLAUDE_USAGE_JSON or CAVEMAN_CLAUDE_SESSION_KEY plus CAVEMAN_CLAUDE_ORG_ID")
	}
	if strings.Contains(orgID, "/") || strings.Contains(orgID, "..") {
		return nil, fmt.Errorf("invalid Claude organization id")
	}
	req, err := http.NewRequest("GET", "https://claude.ai/api/organizations/"+orgID+"/usage", nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("accept", "application/json")
	req.Header.Set("cookie", "sessionKey="+sessionKey)
	client := http.Client{Timeout: 15 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return nil, fmt.Errorf("Claude usage request failed with HTTP %d", resp.StatusCode)
	}
	return io.ReadAll(io.LimitReader(resp.Body, 4<<20))
}

func codexPaths(root string) ([]string, error) {
	paths, _, err := codexPathsUntil(root, nil)
	return paths, err
}

// codexPathsUntil preserves codexPaths' discovery contract while allowing the
// first-run behavior scan to stop a huge cold tree. Other import callers pass no
// deadline and retain their existing exhaustive walk.
func codexPathsUntil(root string, expired func() bool) ([]string, bool, error) {
	var paths []string
	timeBoxed := false
	stopped := func() bool {
		if expired == nil || !expired() {
			return false

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. 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.
  2. Prefer pinning the payload once via CAVEMAN_CLAUDE_USAGE_JSON to avoid depending on live cookie auth.
  3. Wrap the refresh in error handling that tolerates transient failures instead of crashing the surrounding job.
Defensive patterns

Strategy: retry

Validate before calling

// Pre-flight is limited to config; you cannot validate a remote status locally.
// Ensure auth inputs are well-formed before the call:
if os.Getenv("CAVEMAN_CLAUDE_SESSION_KEY") == "" || os.Getenv("CAVEMAN_CLAUDE_ORG_ID") == "" {
    return errors.New("claude usage credentials unset; live fetch will fail")
}

Try / catch

summary, err := s.RefreshClaudeUsageFromEnv()
if err != nil && strings.Contains(err.Error(), "HTTP ") {
    var code int
    fmt.Sscanf(err.Error(), "Claude usage request failed with HTTP %d", &code)
    switch {
    case code == 401 || code == 403:
        return errors.New("session key expired: re-export CAVEMAN_CLAUDE_SESSION_KEY")
    case code == 429:
        time.Sleep(30 * time.Second) // then one retry, not a loop
        summary, err = s.RefreshClaudeUsageFromEnv()
    case code >= 500:
        return fmt.Errorf("claude.ai upstream issue (HTTP %d); retry later", code)
    }
}
if err != nil {
    return err
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Related errors


AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15). Data as JSON: /api/errors/4ec81a7ea6640ef1. Report an issue: GitHub.