{"record":{"id":"cf92117dc6027926","repo":"JuliusBrussee/caveman","slug":"parse-claude-usage-json-w","errorCode":null,"errorMessage":"parse Claude usage JSON: %w","messagePattern":"parse Claude usage JSON: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/internal/store/usage_import.go","lineNumber":117,"sourceCode":"\t\tif event.Basis != observedLocal && event.Basis != \"observed_provider\" {\n\t\t\treturn \"estimated\"\n\t\t}\n\t}\n\treturn observedLocal\n}\n\nfunc (s *Store) RefreshClaudeUsageFromEnv() (ImportSummary, error) {\n\traw := os.Getenv(\"CAVEMAN_CLAUDE_USAGE_JSON\")\n\tif raw == \"\" {\n\t\tfetched, err := fetchClaudeUsageJSON()\n\t\tif err != nil {\n\t\t\treturn ImportSummary{}, err\n\t\t}\n\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}","sourceCodeStart":99,"sourceCodeEnd":135,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/proxy/internal/store/usage_import.go#L99-L135","documentation":"RefreshClaudeUsageFromEnv imports Claude usage/quota data from CAVEMAN_CLAUDE_USAGE_JSON (inline JSON) or, when that env is empty, from a live fetch of claude.ai. This error means the resulting string was not valid JSON — json.Unmarshal failed and the parse error is wrapped with this message. It guards the store from inserting garbage quota events.","triggerScenarios":"Setting CAVEMAN_CLAUDE_USAGE_JSON to a truncated, HTML (login page), or otherwise malformed payload; exporting it with shell quoting that strips or mangles characters; a proxied fetch that returned an error body that was saved verbatim.","commonSituations":"Copy-pasting JSON into an export line and losing closing braces; writing the env var from a script that captured a curl error page instead of the API response; BOM or whitespace-sensitive pipelines; the upstream API changed shape and the saved fixture predates it.","solutions":["Validate the payload first: echo \"$CAVEMAN_CLAUDE_USAGE_JSON\" | jq . — fix whatever jq reports.","Re-capture the payload from the live endpoint (curl the usage URL) so you have a complete, current document.","Use a single-quoted heredoc or write the JSON to a file and export via $(cat file) with proper quoting to avoid shell mangling.","If you meant to use the live fetch path, unset CAVEMAN_CLAUDE_USAGE_JSON entirely instead of leaving a broken value."],"exampleFix":"# before\nexport CAVEMAN_CLAUDE_USAGE_JSON='{\"costs\": [}   # malformed\n\n# after\nexport CAVEMAN_CLAUDE_USAGE_JSON=$(cat usage.json)   # jq-validated file\n# sanity check before running the import:\njq -e . usage.json >/dev/null && echo OK","handlingStrategy":"validation","validationCode":"if raw := os.Getenv(\"CAVEMAN_CLAUDE_USAGE_JSON\"); raw != \"\" {\n    if !json.Valid([]byte(raw)) {\n        return errors.New(\"CAVEMAN_CLAUDE_USAGE_JSON is not valid JSON; validate with jq\")\n    }\n}\nsummary, err := s.RefreshClaudeUsageFromEnv()","typeGuard":"func isValidUsageJSON(raw string) bool {\n    var v any\n    return json.Unmarshal([]byte(raw), &v) == nil\n}","tryCatchPattern":"summary, err := s.RefreshClaudeUsageFromEnv()\nif err != nil {\n    if strings.Contains(err.Error(), \"parse Claude usage JSON\") {\n        return errors.New(\"usage payload malformed: re-capture from the endpoint and jq -e validate it\")\n    }\n    return err // credential/network errors have different fixes\n}","preventionTips":["Always jq -e validate captured payloads before exporting them.","Store payloads in files and export via $(cat file) with correct quoting.","Unset the env var rather than leaving a broken value if switching to live fetch."],"tags":["go","json","environment-variables","usage-import"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}