Skip to content

alarm: "lost more while already alarming" fires when nothing more was lost #11366

Description

@JSONbored

lane_lost_again is the alarm's way of saying a dead-letter queue lost further messages while its issue was already open. It has fired twice in the last 14 days and both times nothing more had been lost — once while the loss was actively shrinking.

Observed

Production, probe-jobs-dlq, from the $exception stream (chronological):

2026-08-14 22:58Z  lane_stale       3 message(s)  (netuid=108, netuid=29, sn-51-lium-revenue-for-validators)
2026-08-14 23:58Z  lane_lost_again  2 message(s)  (netuid=108, netuid=29)   <- count DECREASED
2026-08-15 00:28Z  lane_lost_again  2 message(s)  (netuid=108, netuid=29)   <- count UNCHANGED
2026-08-15 01:58Z  lane_stale       2 message(s)  (netuid=108, netuid=29)

The first lost_again reports new loss on a tick where the queue went from 3 messages to 2. The second reports new loss on a tick where the detail string is byte-identical to the previous one.

Each firing also posts a comment on the open alarm(lane): probe-jobs-dlq issue, so the false signal reaches a human on the noisier of the two channels.

Why

laneAlarmPlan decides "lost more" on a timestamp alone (src/lane-alarm.ts, the update loop):

const record = latest[lane];
if (record.checked_at <= openIssue.updatedAt) continue;
update.push({ lane, issue: openIssue.issue, record });

The block's own comment says "Further losses on a queue whose alarm is already open", and the gate is careful about the right things — it re-checks qualified, the residue guard and the stale floor. But none of those look at what was lost. The only test is that a verdict row exists newer than the last comment.

A dead-letter lane writes a verdict every tick by construction — src/dead-letter.ts is explicit that it "writes stale and never writes ok", because a lost message has no recovery. So checked_at advances past updatedAt on every tick, and the condition is true every tick, whatever the queue is doing.

Intent and implementation disagree: the intent is "more was lost", the implementation is "time passed".

Why it isn't already obvious

Volume is low — 2 firings in 14 days against 156 lane_stale — because the update path also requires an open GitHub alarm for that lane, and those get closed promptly. So the bug only shows during the window an alarm is open, which is exactly when someone is looking at it.

The fix is a comparison, not a clock

The verdict's detail already enumerates the messages (2 dead-lettered message(s) on probe-jobs-dlq (netuid=108,netuid=29)), so the information needed is present — it is just not consulted. A recurrence should require the loss to have changed, and specifically to have grown:

  • comparing detail to the previously reported one catches both observed cases (3→2 differs but shrank; 2→2 is identical);
  • shrinking must not report loss at all. The 3→2 transition shows the count is not monotonic — queue retention ages messages out — so "different" is not sufficient on its own, and a naive inequality would report every ageing-out as a new loss.

Where the previously reported detail lives is the design question. lane_health holds the prior row, so the plan builder could take the previous verdict alongside latest rather than needing new storage.

Not urgent

Two false comments on one issue, and the underlying alarm is correct that the queue has lost messages — netuid=108 and netuid=29 are genuinely dead-lettered and genuinely unrecovered. What is wrong is the claim that they were lost again, which is the kind of thing that teaches a reader to skim the channel.

Found while sweeping live lane health after #11364; probe-jobs-dlq is the one lane currently not ok (1 of 67).

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions