Playbook — Atrax data-freshness false-positive “stale” alert
Trigger: A GitHub issue titled Atrax data stale: ... (label atrax-stale) is opened by the
Atrax Data Freshness workflow, and/or a Still stale: comment shows empty ages
(GPS age: s | cars age: s / GPS age: n/a).
Related: #1371. Sibling pattern: pinbox24 no-logs false positive (a check failing on transient fetch error rather than the thing it claims to measure).
What the alert actually means
.github/workflows/atrax-data-freshness.yml runs every 10 min on the self-hosted bms4 runner and
checks two Supabase tables via the REST API:
| Check | Table | Field | Threshold |
|---|---|---|---|
gps-stale | p24_gps_current_state | n8n_synced_at | < 600 s (10 min) |
cars-atrax-stale | p24_l_cars_atrax | updated_at | < 90000 s (25 h) |
The data is produced by the n8n GPS-sync workflow atrax-kravag-scheduled-fleet-updates
(CCx9UMdphmGficDX on bms-4). When that workflow is healthy, both tables update every ~30–60 s.
Confirm: is the data actually stale? (do this FIRST)
Run against Supabase (mcp__claude_ai_Supabase__execute_sql, project mwkqmgadqnkkihjdeqsi):
SELECT 'gps_current_state' AS check, MAX(n8n_synced_at) AS latest, NOW() - MAX(n8n_synced_at) AS age
FROM p24_gps_current_state
UNION ALL
SELECT 'cars_atrax', MAX(updated_at), NOW() - MAX(updated_at)
FROM p24_l_cars_atrax;- Age is small (seconds/minutes) → the data is fine; this was a false positive (transient Supabase/network blip during the check). The alerting issue auto-closes on the next successful run.
- Age exceeds the threshold → genuine staleness; go to “If genuinely stale” below.
Why the false positives happen
The check fired the stale label whenever curl returned a non-200 / empty body — i.e. a transient
network or Supabase 5xx blip was indistinguishable from real staleness, and there was no retry.
A single hiccup = a false “stale” alert with empty ages. Compounded by the bms4 self-hosted runner
backlog (scheduled runs queueing for hours, then running a stale workflow snapshot).
The fix (shipped in #1371)
atrax-data-freshness.yml now:
- Retries the Supabase call:
curl --retry 4 --retry-delay 5 --retry-all-errors --max-time 25. - Captures the HTTP status (
-w '%{http_code}') and distinguishes three states per check:ok/stale(age measured > threshold) /fetch-error(could not reach Supabase). - Only a measured
stalestate opens/keeps theatrax-staleissue. A purefetch-erroris logged as a run::warning::and does not page — if data is truly going stale, the next run that reaches Supabase reports it correctly. - Real ages (or
n/a) and a human-readable reason are included in the issue/comment body.
If genuinely stale (age > threshold)
- Open the n8n GPS-sync workflow: https://n8n.bms-4.infra.zintegrowana.online/workflow/CCx9UMdphmGficDX
(
atrax-kravag-scheduled-fleet-updates). Check the last execution + error. - Common causes: expired
ATRAX_AUTH_STRINGcredential, Atrax API 409/replate errors (seeatrax-fleet-sync-409-replate.md), a revoked Supabase key in the n8n credential after a Supabase key rotation (401 Unregistered API keyin the execution — seen8n/n8n-supabase-credential-rotation.md), or a bms-4 n8n worker stall. - Trigger a manual run (idempotent). Re-run the freshness check:
gh workflow run atrax-data-freshness.yml --repo radieu/p24-infra. - Once data is fresh, the workflow auto-closes the
atrax-staleissue.
Prevention / escalation
- A flood of
Still stale:comments with empty/n/aages after this fix means the Supabase REST API or theSUPABASE_SERVICE_KEYis genuinely failing — check Supabase status and the key validity (curl .../rest/v1/should return 200). Escalate via Discord + ahuman-actionissue. - Persistent
cancelled/queued freshness runs → the bms4 self-hosted runner is backlogged; check runner health on bms-4.