JuliusBrussee/caveman · error

parse Claude usage JSON: %w

Error message

parse Claude usage JSON: %w

What it means

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.

Source

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

		if event.Basis != observedLocal && event.Basis != "observed_provider" {
			return "estimated"
		}
	}
	return observedLocal
}

func (s *Store) RefreshClaudeUsageFromEnv() (ImportSummary, error) {
	raw := os.Getenv("CAVEMAN_CLAUDE_USAGE_JSON")
	if raw == "" {
		fetched, err := fetchClaudeUsageJSON()
		if err != nil {
			return ImportSummary{}, err
		}
		raw = string(fetched)
	}
	var v any
	if err := json.Unmarshal([]byte(raw), &v); err != nil {
		return ImportSummary{}, fmt.Errorf("parse Claude usage JSON: %w", err)
	}
	quotas := quotaEventsFromAny("anthropic", "claude_usage_link", "linked_api", time.Now().UTC().Format(time.RFC3339), v)
	n, err := s.InsertQuotaEvents(quotas)
	if err != nil {
		return ImportSummary{}, err
	}
	return ImportSummary{Source: "claude", QuotaImported: n, Basis: "linked_api"}, nil
}

func fetchClaudeUsageJSON() ([]byte, error) {
	sessionKey := os.Getenv("CAVEMAN_CLAUDE_SESSION_KEY")
	orgID := os.Getenv("CAVEMAN_CLAUDE_ORG_ID")
	if sessionKey == "" || orgID == "" {
		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")
	}

View on GitHub (pinned to 27d5a3981a)

Solutions

  1. Validate the payload first: echo "$CAVEMAN_CLAUDE_USAGE_JSON" | jq . — fix whatever jq reports.
  2. Re-capture the payload from the live endpoint (curl the usage URL) so you have a complete, current document.
  3. Use a single-quoted heredoc or write the JSON to a file and export via $(cat file) with proper quoting to avoid shell mangling.
  4. If you meant to use the live fetch path, unset CAVEMAN_CLAUDE_USAGE_JSON entirely instead of leaving a broken value.

Example fix

# before
export CAVEMAN_CLAUDE_USAGE_JSON='{"costs": [}   # malformed

# after
export CAVEMAN_CLAUDE_USAGE_JSON=$(cat usage.json)   # jq-validated file
# sanity check before running the import:
jq -e . usage.json >/dev/null && echo OK
Defensive patterns

Strategy: validation

Validate before calling

if raw := os.Getenv("CAVEMAN_CLAUDE_USAGE_JSON"); raw != "" {
    if !json.Valid([]byte(raw)) {
        return errors.New("CAVEMAN_CLAUDE_USAGE_JSON is not valid JSON; validate with jq")
    }
}
summary, err := s.RefreshClaudeUsageFromEnv()

Type guard

func isValidUsageJSON(raw string) bool {
    var v any
    return json.Unmarshal([]byte(raw), &v) == nil
}

Try / catch

summary, err := s.RefreshClaudeUsageFromEnv()
if err != nil {
    if strings.Contains(err.Error(), "parse Claude usage JSON") {
        return errors.New("usage payload malformed: re-capture from the endpoint and jq -e validate it")
    }
    return err // credential/network errors have different fixes
}

Prevention

When it happens

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

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

Related errors


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