{"record":{"id":"3eef29d0b056fed1","repo":"JuliusBrussee/caveman","slug":"claude-usage-refresh-needs-caveman-claude-usage-js","errorCode":null,"errorMessage":"Claude usage refresh needs CAVEMAN_CLAUDE_USAGE_JSON or CAVEMAN_CLAUDE_SESSION_KEY plus CAVEMAN_CLAUDE_ORG_ID","messagePattern":"Claude usage refresh needs CAVEMAN_CLAUDE_USAGE_JSON or CAVEMAN_CLAUDE_SESSION_KEY plus CAVEMAN_CLAUDE_ORG_ID","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/internal/store/usage_import.go","lineNumber":131,"sourceCode":"\t\traw = string(fetched)\n\t}\n\tvar v any\n\tif err := json.Unmarshal([]byte(raw), &v); err != nil {\n\t\treturn ImportSummary{}, fmt.Errorf(\"parse Claude usage JSON: %w\", err)\n\t}\n\tquotas := quotaEventsFromAny(\"anthropic\", \"claude_usage_link\", \"linked_api\", time.Now().UTC().Format(time.RFC3339), v)\n\tn, err := s.InsertQuotaEvents(quotas)\n\tif err != nil {\n\t\treturn ImportSummary{}, err\n\t}\n\treturn ImportSummary{Source: \"claude\", QuotaImported: n, Basis: \"linked_api\"}, nil\n}\n\nfunc fetchClaudeUsageJSON() ([]byte, error) {\n\tsessionKey := os.Getenv(\"CAVEMAN_CLAUDE_SESSION_KEY\")\n\torgID := os.Getenv(\"CAVEMAN_CLAUDE_ORG_ID\")\n\tif sessionKey == \"\" || orgID == \"\" {\n\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)","sourceCodeStart":113,"sourceCodeEnd":149,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/proxy/internal/store/usage_import.go#L113-L149","documentation":"When CAVEMAN_CLAUDE_USAGE_JSON is not set, fetchClaudeUsageJSON tries the live claude.ai usage endpoint and requires both CAVEMAN_CLAUDE_SESSION_KEY (browser session cookie value) and CAVEMAN_CLAUDE_ORG_ID. Missing either yields this fail-fast configuration error before any HTTP request is made — no anonymous or partial fetch exists.","triggerScenarios":"Running the usage refresh with only CAVEMAN_CLAUDE_SESSION_KEY exported, only CAVEMAN_CLAUDE_ORG_ID, or neither; a systemd/CI unit that does not import the file where these are defined.","commonSituations":"Operator sets the session key but never noted the org id (visible in the claude.ai URL or settings); env vars defined in an interactive shell but the import runs from cron; the session key env name is typo'd so both appear unset.","solutions":["Export both variables: CAVEMAN_CLAUDE_SESSION_KEY=<sessionKey cookie value> and CAVEMAN_CLAUDE_ORG_ID=<org uuid>.","Prefer the offline path if you already have the JSON: set CAVEMAN_CLAUDE_USAGE_JSON instead and skip credential handling.","Verify with: [ -n \"$CAVEMAN_CLAUDE_SESSION_KEY\" ] && [ -n \"$CAVEMAN_CLAUDE_ORG_ID\" ] && echo ok.","Check for typos/trailing whitespace in the variable names and values in your env file."],"exampleFix":"# before\nexport CAVEMAN_CLAUDE_SESSION_KEY=sk-ant-sid-...\n# org id missing -> error\n\n# after\nexport CAVEMAN_CLAUDE_SESSION_KEY=sk-ant-sid-...\nexport CAVEMAN_CLAUDE_ORG_ID=0f1e2d3c-4b5a-6978-8796-a5b4c3d2e1f0","handlingStrategy":"validation","validationCode":"func claudeUsageCredsReady() bool {\n    if os.Getenv(\"CAVEMAN_CLAUDE_USAGE_JSON\") != \"\" {\n        return true\n    }\n    return os.Getenv(\"CAVEMAN_CLAUDE_SESSION_KEY\") != \"\" &&\n        os.Getenv(\"CAVEMAN_CLAUDE_ORG_ID\") != \"\"\n}\n\nif !claudeUsageCredsReady() {\n    return errors.New(\"set CAVEMAN_CLAUDE_USAGE_JSON, or both SESSION_KEY and ORG_ID\")\n}","typeGuard":null,"tryCatchPattern":"if _, err := s.RefreshClaudeUsageFromEnv(); err != nil {\n    if strings.Contains(err.Error(), \"CAVEMAN_CLAUDE_SESSION_KEY\") {\n        return configError(\"export CAVEMAN_CLAUDE_SESSION_KEY and CAVEMAN_CLAUDE_ORG_ID (or CAVEMAN_CLAUDE_USAGE_JSON)\")\n    }\n    return err\n}","preventionTips":["Add a preflight env check in automation before invoking the refresh.","Keep the two credentials together in one env file so they are never half-set.","Prefer the JSON payload path in CI to avoid live credential dependency."],"tags":["go","environment-variables","configuration","auth","usage-import"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}