| name | triage-warning |
| description | Use when triaging a log_warning, a PostHog $exception, or an auto-filed error-tracking GitHub issue (body contains "posthog-issue-") — decide expected no-op vs real defect and apply the escalation contract. |
Triaging a warning / $exception
Decision tree — the contract is "once is a warning, repeatedly is a defect" (utilities/log_escalation.py promotes 3 repeats in 24h to ERROR + ONE grouped $exception; the daily cron then files a GitHub issue):
- Already-fixed test-suite leakage? A group filled by the pytest leak (#1451/#1460/#1498) is still
active in PostHog and still gets hand-filed as a production bug. Three tells: the message is a fixture string (grep -rn "<message>" tests/ finds a side_effect, src/ never raises it), sampled events carry distinct_id 42 (the unit-lane SESSION_USER_ID), and last_seen is on or before 2026-08-14 UTC (the last of the three guards, #1498, merged 21:26Z that day). All three → resolve the PostHog group, close the issue, change no code (docs/error-tracking.md, #1673).
- Expected no-op (working behaviour that merely didn't apply — a group feed with no sort control, a user with no refresh token, an absent optional element)? → downgrade the call to
log_debug inside the owning resolver — never delete the log line, never leave it as a warning. Mark the PostHog issue resolved.
- Real defect? → fix the cause, keep the warning. The escalation exists precisely to surface slow-burn breakage (a selector missing for 3 days ≠ a one-off blip).
- Dedup-key hygiene when writing/editing warning messages: volatile tokens (URLs, ids, numbers, quoted strings) are masked before fingerprinting, and the key includes
module.function. Keep distinct failures distinct — step names stay quote- and digit-free, and two different broken selectors must not share one message shape.
- Only the true owner logs a failure — a caller must not restate what the callee already warned about (double-counting forks issues).
- Never
capture_exception an HTTPException (4xx is a response, not an issue); use observability.capture_exception(...) only for caught-and-not-reraised errors.
Auto-filed issues carry posthog-issue-<id> (the dedup marker — leave it in the body) and a PostHog link for the stack trace; browser errors also link a session replay. Cron: scripts/error_to_issues.sh daily 08:30, wrapping scripts/posthog_error_issues.py.
A recurring warning already tracked by an OPEN issue arrives as a comment on that issue, not a new one (#1083) — so triage it on the thread you're already on, and the marker in that comment is what stops the next run duplicating it. If the match was wrong (the string matched a different defect), open a separate issue for it and LEAVE the comment — the match is on the warning TEXT, which the matched issue still carries, so deleting the comment only makes the next run post it there again.
Authoritative: docs/error-tracking.md, src/cqc_lem/utilities/CLAUDE.md (level table + escalation contract).