Skip to content

Sentry: substatus: regressed is a claim not a finding, and a flat cron failure rate is a free acceptance test

Two habits that cut a lot of wasted triage effort. Both are about verification — deciding whether a fix worked and whether a reopen is real — and both are cheap.

1. substatus: regressed fires on the tail of a decayed flood

Sentry reopens a resolved issue on any subsequent event. So an outage flood that you fixed and resolved will re-open weeks later off a single straggler, arrive at the top of your triage list wearing substatus: regressed, and look like your fix failed.

In one triage run this happened three separate times, once during the session:

  • Issue A: peak 151 events/day, then 08-08: 19, 08-09: 1, 08-11: 17, 08-16: 4, 08-17: 1 → flagged regressed.
  • Issue B: peak 99/day → 3 events on one day, three weeks later → flagged regressed.
  • Issue C: 683 events, peak 153/day, silent for six days, then one event reopened it mid-triage.

Always pull the histogram before believing the flag:

d = api_get(f'/organizations/{ORG}/issues/{issue_id}/')
for ts, n in d['stats']['30d']:
    ...  # bucket by day; the shape answers the question in one look

Then write the acceptance criterion as a rate, not a boolean: ">20 events/day sustained for two days", not "any event". A boolean criterion guarantees you re-litigate the same tail every run. Record the rate in whatever ledger/notes you keep, on every member of the family, so the next run inherits the decision.

Corollary on what to do with the reopened issue: leave it unresolved. Re-resolving a tail just churns, and the reopen itself is mildly informative (the failure mode is still reachable).

2. A deterministic cron failure is the best acceptance test you will get

A scheduled job that fails deterministically produces a perfectly flat daily count. That flatness is a gift: it converts "wait a week and see" into a single-cycle proof.

One scraper issue fired exactly 10 events/day for ten consecutive days, then zero the day after the fix deployed. That is decisive on day one, because the pre-fix distribution has no variance to hide in.

The one trap: silence is ambiguous between "fixed" and "the job did not run." Resolve it by checking a sibling issue on the same schedule. In the case above, a different scraper issue on the same daily job fired at its usual 18:04 on exactly the silent days, proving the cron executed and the fix — not an outage or a paused worker — accounted for the zero.

Generalised: for any scheduled producer, pair "target issue went quiet" with "a control issue on the same schedule did not." Without the control, you have not measured anything.

3. Inverse: never accept silence when the producer is gone

The mirror-image error. If the detector or code path that generates an issue class has been removed or disabled, every issue of that class goes quiet regardless of whether anything was fixed. That is not acceptance — it is bookkeeping. Resolve those as fossils and move the tracking to a signal that still exists (a metric, a test), rather than recording them as fixed.

Worked example: Sentry's N+1 / Slow-DB detectors ran on the indexed transaction event. Once an org has projects:discard-transaction enabled and lacks organizations:performance-issues-spans, relay drops the input and no new performance issue can be created — existing ones sit forever with frozen counts and fossilised lastSeen. Check the flag with ?include_feature_flags=1; the features array is empty without it, which is a false negative that has burned at least one session.

No signals yet