fix(ops): a message is worth sending only if a human must act on it - #433
Merged
Conversation
Thirteen Telegrams reached George on the evening of 2026-08-28. Two of them were real, and one of those two was an outage nobody had told him about in a way he could act on. This makes every category of the other eleven impossible. - lib-alert: one delivery point. alert_once/alert_clear give every sender a per-subject cooldown; an identical-text floor sits under the UNKEYED path so a caller with no state (the register check, which sent the same failure twice in 60s) still cannot repeat itself. Keyed senders bypass the floor — two mechanisms arguing about one message means the quieter one silently wins. ALERT_DRY_RUN=1 journals without delivering, because at 22:14 a test of a new check rang a real phone with a message saying it was a test. Suppressed is never invisible: the journal gets every alert, including the suppressed ones. - notify-failure: a oneshot that a retry has already fixed is not an incident. restic-check failed at 18:41:42 on a 25-day-old stale lock, was re-run at 18:42:28, finished clean at 18:44:46 — and paged, because OnFailure fires on the failing run and nothing looked again. It looks again. The unit template gains TimeoutStartSec: DefaultTimeoutStartSec is 90s, so the notifier would otherwise be killed inside its own grace window and the page never sent. - host-check: an app whose .env its own unit User= cannot read is REPAIRED and reported, not asked about. This took two apps down today (vitareba 18:38, botsmann 20:16, both root-owned .env, both crash-looping) and both times the remedy was one deterministic command — the owner is not a guess, it is the unit's User=. deploy.sh asserts the same invariant where the file is written. - cloud-init-hotplugd is masked. Failed since 2026-07-22 with nothing to do, it was the member that pinned the old aggregate latch at `bad` for six weeks; per-unit keying turned that silence into a recurring page. A unit that can only produce noise should not be in the failed set at all, so that `systemctl --failed` keeps meaning "something is wrong". - fleet-register-check moves into the repo (it was untracked, which is why neither of its bugs was ever reviewed) and now judges the register as COMMITTED. It read a working tree that ~15 agent sessions share and paged twice about a duplicate port that had never been committed. CI still judges the tree, which is right there — in CI the tree IS the commit under review. Six mutations, each reintroducing one of the above, each turning the suite red. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AqzRcMP1uJzd7Tav5fNxQz
watch.sh carried a byte-identical alert(), so every property added to the shared one — the duplicate floor, ALERT_DRY_RUN, the journal-always guarantee — silently did not apply to the watchdog's DOWN/RECOVERED messages. A second copy of a send path is exactly how the fleet register check ended up able to page twice in sixty seconds with no state at all: nothing is wrong with the copy on the day it is made, and nothing updates it afterwards. Falls back to journal-only if lib-alert.sh is somehow absent, rather than going silent. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AqzRcMP1uJzd7Tav5fNxQz
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Thirteen Telegrams reached George on the evening of 2026-08-28. Two were real — and one of those two, botsmann being down, was buried in the noise of the other eleven. This makes every category of the eleven impossible, and fixes the outage class that produced the two.
What each of last night's messages was, and what happens to it now
UNIT DOWN: vitareba-appin 60salert_onceUNIT DOWN: restic-checkExecStartPre=-restic unlockon the boxFAILED UNIT: ●/RECOVERED: unit____FAILED UNIT: cloud-init-hotplugdzz-audit-probefailed + recoveredALERT_DRY_RUN=1: probes journal, they do not deliverfleet register check failed on TESTregister check failed … substrata port 4022UNIT DOWN: botsmann-app/opt/botsmann/shared/.envwas root-owned, app runs asubuntuThe changes
lib-alert.sh— one delivery point.alert_once/alert_cleargive every sender a per-subject cooldown. An identical-text floor sits under the unkeyed path only, so a caller with no state cannot repeat itself; keyed senders bypass it, because two mechanisms arguing about one message means the quieter one silently wins. Suppressed is never invisible — the journal gets every alert, including suppressed ones.notify-failure.sh— a oneshot a retry already fixed is not an incident. The unit template gainsTimeoutStartSec=300:DefaultTimeoutStartSecis 90s, so the notifier would otherwise be SIGTERMed inside its own grace window and the page never sent — an anti-noise guard that becomes an anti-alert bug.host-check.sh— an app whose.envits own unitUser=cannot read is repaired and reported, not asked about. This took two apps down on 2026-08-28 (vitareba 18:38, botsmann 20:16) and both times the remedy was one deterministic command. The owner is not a guess: it is the unit'sUser=.deploy.shasserts the same invariant where the file is written.scripts/local/fleet-register-checkmoves into the repo — it was untracked, which is why neither of its bugs was ever reviewed.Verification
npm run test:ops: 52 passed, 0 failed in the host-alert suite (33 before), whole bundle green.0 inert. Mutations ran against a throwaway copy; the pushed artifact was grepped before and after.not repeating.🤖 Generated with Claude Code
https://claude.ai/code/session_01AqzRcMP1uJzd7Tav5fNxQz