Fixed
Retry-storm protection for the report pipeline.
- Exponential backoff on all transient failures, not just HTTP 429: now covers 5xx server errors, 408 (Request Timeout), 499 (nginx "Client Closed Request") and cURL/connection errors. Previously the server kept hammering an overloaded API on 502/503/499 with no backoff, amplifying the overload.
- Backoff state is now persisted to
data/report_backoff.json. Under the web-cron model every page visit builds a freshReportClient, so the former in-memory backoff never survived a single request and was effectively useless. - 499 and 408 are no longer permanent rejections — such reports are retried after a backoff instead of being dropped.
- Rate limit (default 60/min) is now persisted to
data/report_ratelimit.jsonand advanced atomically viaflock, enforcing the cap globally across all requests/processes instead of within a single client instance. ReportQueuestops batches cleanly while a backoff or rate limit is active, instead of inflating thefailed_attemptscounter without sending anything.