Skip to content

fix(ops): one incident is one message ACROSS detectors, not just within one - #435

Merged
github-actions[bot] merged 1 commit into
mainfrom
fix/one-incident-one-message
Aug 29, 2026
Merged

fix(ops): one incident is one message ACROSS detectors, not just within one#435
github-actions[bot] merged 1 commit into
mainfrom
fix/one-incident-one-message

Conversation

@catomean

Copy link
Copy Markdown
Collaborator

This morning's messages, after yesterday's fix. Three of the four were still avoidable.

Time Message Verdict
00:02 🧹 DISK GC: freed 2296MB (81% → 78%) routine maintenance succeeding — nothing to act on
06:00 Weather / Calendar / Action Items OpenClaw, not this stack — the daily brief, left alone
06:45 ⚙️ FAILED UNIT: orangecat-cat-outcomes.service real, from the sweep
06:46 🔴 UNIT DOWN: orangecat-cat-outcomes.service the same incident again, from the notifier

The duplicate my last PR did not catch

Both detectors were individually correct, and each was correctly deduplicated against itself — which is exactly why keying per detector let one fact produce two messages. The cooldown key is now the subject (the unit), shared between the sweep and the notifier. Whichever notices first speaks; the other finds the claim taken and journals instead. Neither needs to know the other exists.

Closure only for failures that were announced

✅ RECOVERED: unit____ reached George yesterday: closure on a message nobody received, naming a state key instead of a unit. Recoveries now name the unit and are sent only if the failure was actually delivered.

The nightly GC report

freed 2296MB (81% → 78%) is the box looking after itself. The disk check already pages above 85%, so a GC that lands back under the mark has by definition removed the reason to say anything — journal. It still pages when the outcome is not routine (still above the mark, or nothing left to reclaim). Its test suite gained a delivery capture: with only a logger stub, moving a message from alert() to logger() looks identical to leaving it alone.

Verification

  • host-alert suite 60 passed (was 52), disk-gc 22 passed (was 20), whole test:ops bundle green.
  • Three mutations, each turning its suite red, run against throwaway copies: sweep keyed on itself → the cross-detector tests go red; closure for an unannounced failure → the closure test goes red; routine GC paging again → the delivery test goes red. 0 inert.

Note on the one real alert

orangecat-cat-outcomes is a false positive and is fixed separately in the orangecat repo. It claims "the cat_action_log write path looks dead", but the log has 10 rows in the window, the newest from 2026-08-25. Its predicate counts only status=completed + create_* rows, then reports a cause it never tested.

🤖 Generated with Claude Code

https://claude.ai/code/session_01AqzRcMP1uJzd7Tav5fNxQz

…in one

06:45 `⚙️ FAILED UNIT: orangecat-cat-outcomes.service` from the host-check
sweep. 06:46 `🔴 UNIT DOWN: orangecat-cat-outcomes.service` from the OnFailure
notifier once its grace expired. One incident, two messages — and each detector
was individually correct and correctly deduplicated against itself, which is
exactly why this survived yesterday's fix. Keying the cooldown per detector
still lets N detectors send N messages about one fact.

- The cooldown key is now the SUBJECT (the unit), shared between the sweep and
  the notifier. Whichever notices first speaks; the other finds the claim taken
  and goes to the journal. Neither has to know the other exists.

- Closure is only sent for a failure that was actually announced. `✅
  RECOVERED: unit____` reached George yesterday — closure on a message nobody
  received, naming a state key instead of a unit. Recoveries now name the unit,
  and say nothing when nothing was said.

- The nightly disk GC no longer pages when it succeeds. "freed 2296MB (81% →
  78%)" at 00:02 is the box looking after itself and asking nothing of anyone;
  the disk check already pages above 85%. It still pages when the outcome is
  NOT routine — space reclaimed but still above the mark, or nothing left to
  reclaim. Its test suite gained a delivery capture, because with only a logger
  stub, moving a message from alert() to logger() is indistinguishable from
  leaving it alone.

test:ops 60 passed in the host-alert suite (was 52) and 22 in disk-gc (was 20).
Three mutations — sweep keyed on itself, closure for an unannounced failure,
routine GC paging again — each turn their suite red.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AqzRcMP1uJzd7Tav5fNxQz
@github-actions
github-actions Bot merged commit f313df1 into main Aug 29, 2026
3 checks passed
@github-actions
github-actions Bot deleted the fix/one-incident-one-message branch August 29, 2026 05:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant