Alert Fatigue: 82 Emails in a Day From My Security Monitor

How many alerts should a one-person security monitor send? On my numbers, about one email a day, plus the rare one that asks me to do something. On November 1 my monitor handed 74 emails to the mail service between midnight and 13:32 Pacific: 55 for blocks it had already made, 16 critical alerts, two hourly volume alerts, and one from the scanner on my Mac.12 My reading that afternoon was that every one described routine scanning the edge had already stopped;3 for the critical floods, section 6 shows that reading was too generous. The fix was a policy, not a threshold: one daily digest that doubles as the heartbeat, blocks to chat instead of mail, a critical folded into the digest when the edge already handled it, and the outcome written on each cloud critical. The next day a scanner set off four criticals in under six minutes across my sites, and my monitor sent zero emails about it.4 The day still produced 19 emails, not one, and the reason is the most useful part of the story.5

I am the only reader of my monitor’s mail. When the inbox said 74, I asked whether these were routine or real, and the honest answer was that the monitor could not tell me from the subject lines, because every subject said the same thing at a different volume.1 A monitor whose one reader stops reading it has stopped monitoring, whatever its detectors still catch. What follows is the count, the policy that replaced it, a test case, and the three places where the clean version of the story breaks.

TL;DR

  • 74 emails by 13:32, 82 by midnight. My own table and the count derived from the logs agree on 74 before I complained (55 per-block, 16 critical, two hourly, one local); the whole of November 1 came to 81 logged plus one digest I sent by hand.125
  • 22 of the 74 are unproven. They came from code that logged “sent” without checking the mail service’s answer, so the log proves a hand-off was attempted, not that a message arrived.26
  • The literature already had the rule. Google’s SRE book allows three outputs (alerts, tickets, and logging), says “Every page should be actionable,” and calls email alerts “alert spam”; the Joint Commission’s alert cites an estimate that “between 85 and 99 percent of alarm signals do not require clinical intervention”.78910
  • The test case worked. On November 2 one address from a Dutch hosting provider raised four criticals in 5 minutes 51 seconds across my sites; the monitor blocked it once, posted the block to chat, folded every critical into the digest, and sent no email.41112
  • The next day still sent 19, not one. Fourteen were critical WAF-flood alerts in which some events had no block or challenge recorded, and their subjects said “WAF blocks” even when the edge had blocked none of the events (twice that day; the evening before, one said “104 WAF blocks” when the edge had blocked or challenged nothing); my first rewording, the day it was counted, still mixed two counts in one line, and the review of this post caught it.51314
  • Two signals were wrong. A code-signing check on my Mac called Apple’s own Spotlight daemon an unsigned crypto miner, and the digest’s “RUNS SHORT 162/207” turned out to be part sleep, part a slower real cadence, and part, by the timing, three watchdog kills.151617
  • What changed: one digest a day carries the heartbeat, every block, and every cloud critical’s outcome, while only a critical neither fold covers, the hourly volume alert, and the local scanner still email at once; 82 emails on November 1 became 19 on November 2.185

1. The inbox on November 1

At 13:32 Pacific on November 1 I wrote to the agent session that runs my infrastructure: the monitor’s emails were spamming my inbox; was there anything to change, and were these routine things or real threats?1 The table I kept that afternoon counted 74 emails since midnight by sender: 55 one-per-block notices, 16 critical alerts, two hourly volume alerts, and one critical from the scanner on my Mac.1

The count rests on a rule, so here it is before the numbers. An email, for this post, is a line in the monitor’s log that records a hand-off to the mail service. A script walked the six log files for October 31 through November 2 and classified every such line by stream and by outcome.5 Run to 13:32, the script agrees with my table stream for stream: 55, 16, two, and one, total 74.2 The whole local day, to midnight, came to 81 logged emails (55 per-block, 22 critical, three hourly, one local), plus one digest I sent by hand at 13:48, which has no send line in the monitor’s log, for 82.51

Two details inside the agreement matter. My table said “9 before the fold went live at 00:30 PT; the 7 after” for the criticals; the log has 11 critical “sent” lines before 00:30 and five after, and the first critical folded into a block notice appears at 00:54:43, after the fold’s change landed at 00:46 PDT, not the 00:30 my table named.218 The totals match; the split I wrote by hand was off by two. The second detail is larger. Of the 74, 22 (10 per-block, 11 critical, one hourly) came from senders that piped the mail service’s answer away and then logged “sent” no matter what came back.26 A fix at 01:04 PDT that morning, before the clocks fell back, made the cloud half’s senders log the service’s HTTP status and say “sent” only on a 2xx; the local scanner’s sender writes a bare “Critical email sent” line, and only when the request raised no error.6 The 22 earlier lines prove that the monitor tried; they do not prove that the mail service accepted anything. My log for October 31 judged that the cloud emails “most likely were” delivered, which is a reasonable guess and still a guess.19

Even a 2xx proves less than the word “sent” suggests. Resend’s documentation defines its email.sent event as “Occurs whenever the API request was successful. Resend will attempt to deliver the message to the recipient’s mail server,” and lists delivery as a separate, later event.20 The send call itself answers with an id.21 Every count below is a count of hand-offs the mail service accepted, or, for the 22 on November 1 and the 10 on October 31, 32 in all, hand-offs the monitor attempted.5 None of them is a count of messages I read.

By hour, the day looks like a cliff and a plateau.

Bar chart of November 1 by local hour, with the 01:00 hour shown twice for the end of daylight time. Blue bars are emails sent: 22 in the midnight hour, then 8, 5, 5, 4, 2, 4, 3, 4, 4, 3, 3, 3, 3 and 1 by the 13:00 hour. A dashed line at 13:50 marks the one-email-a-day change; after it the blue bars read 1, 0, 2, 0, 1, 1, 1, 0, 1 and 0. Gray bars are alerts folded or suppressed instead of emailed: between 1 and 8 an hour before the change and between 3 and 23 an hour after it.

November 1, local time: emails per hour against alerts that did not become email, with the 13:50 policy change marked.22

The first hour carried 22 emails, all from the unverified code (10 per-block, 11 critical, one hourly).22 From there the per-block notices ran at one to five an hour, because the edge kept blocking scanners and every block wrote me a letter. The last per-block email left at 13:25:57; the first per-block notice folded into the digest instead appears at 14:20:35.22 After the 13:50 change the emails fall to between zero and two an hour while the folded and suppressed alerts climb to as many as 23 an hour.22 The traffic did not fall. The emails did.

The day before is a poor baseline. My log for November 1 says “Yesterday had 10,” and the script agrees, but the October 31 log starts at 22:31, the evening I re-armed the monitor, so those 10 emails cover 89 minutes.15 On my reading the 74 measured the detector working on a full day of internet background, not a change in what the scanners were doing.

2. What the alerts were for

The monitor has two halves. The cloud half is scheduled to run on my Mac every five minutes and asks Cloudflare for the firewall events and the grouped request counts on each of my zones: which addresses hit which paths, how often, and what action the edge took on each event.1823 Cloudflare’s own description of the data is the right frame for it: “Security Events displays information about requests actioned or flagged by Cloudflare security products, including features such as Browser Integrity Check.”24 The local half is a scanner that checks the Mac itself in tiers every 10 minutes: processes and their code signatures, the agent hooks on the machine, the tools that shadow system binaries on its path, and the like.12

What the cloud half does with the data is simple to state. It raises findings by severity (critical, high, medium) and category, and for a critical it tries to act before it tells anyone: when one address is behind enough of the events, it adds that address to an account-wide block list that every zone enforces at the edge, for seven days.124 Until the November 1 changes, every one of those acts also produced an email, and so did every critical that a two-hour limit per site and category did not suppress, the end of every session, and every hour with heavy blocking.185 The local scanner raises its own findings and emails its criticals itself.25 Cloudflare’s own Alerts come in two kinds, “Monitors that you define yourself, and Platform Notifications”: scheduled SQL API queries and pre-configured product alerts, both set up in its dashboard.26 Mine is neither: a script on my Mac that reads the firewall events itself, which also means the noise is mine to fix.

3. What the literature already said

None of what follows is new, which is the uncomfortable part. I had read most of it and built the email-per-event monitor anyway.

Three outputs, and email is not one of them. The introduction to Google’s Site Reliability Engineering says “There are three kinds of valid monitoring output”: alerts, which “Signify that a human needs to take action immediately”; tickets, which “Signify that a human needs to take action, but not immediately.”; and logging, where “No one needs to look at this information, but it is recorded for diagnostic or forensic purposes.”7 The same page is blunt about the design I had: “a system that requires a human to read an email and decide whether or not some type of action needs to be taken in response is fundamentally flawed.”7 Sorted that way, my 55 per-block emails were logging delivered as alerts. The edge had acted; nothing was left for a human to do.

Every page should be actionable. Rob Ewaschuk’s chapter on monitoring in the same book defines an alert as “A notification intended to be read by a human and that is pushed to a system such as a bug or ticket queue, an email alias, or a pager,” and its footnote on the email kind reads “Sometimes known as “alert spam,” as they are rarely read or acted on.”9 The chapter’s rules for pages fit in three lines: “Every page should be actionable.” “I can only react with a sense of urgency a few times a day before I become fatigued.” “If a page merely merits a robotic response, it shouldn’t be a page.”8 My robotic response to a per-block email was to delete it. The chapter also names the cost of the noise: “When pages occur too frequently, employees second-guess, skim, or even ignore incoming alerts, sometimes even ignoring a “real” page that’s masked by the noise.”27 And it describes a team that “spent significant amounts of time triaging the alerts to find the few that were really actionable,” which is a fair description of my November 1 afternoon.28

Symptoms over causes, and a test for a page. Ewaschuk’s earlier essay, which he says formed the foundation for that chapter, compresses the test to one sentence: “Pages should be urgent, important, actionable, and real.”29 On causes it says “Include cause-based information in symptom-based pages or on dashboards, but avoid alerting directly on causes,” under a section titled “Cause-based alerts are bad (but sometimes necessary).”29 I read a firewall block as a cause-level fact: a scanner hit a rule. The symptom I care about is a sensitive path served or a site down, and on my reading none of the 74 reported either; that day’s probe-path requests showed no 2xx on a sensitive path.3 The essay also anticipates the digest: “A daily (or more frequent) report can work too,” with the warning that “without a system of accountability this amounts to less-spammy email alerts”.30 His remedy is a person: “make sure the oncall person (or someone else) is designated to triage these every day”.30 On a one-person monitor that person can only be me. The outcome field in section 4 is my own supporting mechanism, not his: it gives the daily triage something to check, what became of each critical, but it does not do the triage.

Precision and recall. The Site Reliability Workbook’s chapter on alerting gives the vocabulary for what went wrong. Precision is “The proportion of events detected that were significant.” Recall is “The proportion of significant events detected.”31 I did not measure my monitor’s recall, which needs a count of the significant events it missed, and section 5 describes one it missed. As a mail stream its precision was near zero: 74 emails, and on my reading no message among them asked me to do anything. The same chapter names reset time, “How long alerts fire after an issue is resolved. Long reset times can lead to confusion or to issues being ignored.”31 Reset time is defined for alerts that keep firing; a per-block email fires once and then carries no resolution update: it never says the scanner left.

The hospital version of the same failure. The SRE chapter itself points outside software, to cardiac alarm management, “for an example of alert fatigue in another context.”28 The Joint Commission’s Sentinel Event Alert on medical device alarm safety, issued April 8, 2013, puts the numbers on it: “The number of alarm signals per patient per day can reach several hundred”, and “It is estimated that between 85 and 99 percent of alarm signals do not require clinical intervention”.10 The alert is careful in a way worth copying: the 85 to 99 percent covers alarms that need no intervention, not only false ones, and the alert does not split the two. The consequence it records is the one I was halfway into: clinicians “become desensitized or immune to the sounds,” and “may turn down the volume of the alarm, turn it off, or adjust the alarm settings outside the limits that are safe and appropriate for the patient”.10 It counts 98 alarm-related events from January 2009 to June 2012, of which 80 “resulted in death,” and, beyond the factors reported to its database, names alarm fatigue “the most common contributing factor.”10 My stakes are far lower. The mechanism is the same: a person who learns that alarms do not need them learns it about the next real one too.

NIST, then and now. The 2012 revision of NIST’s incident handling guide, SP 800-61r2, warned that “it is not uncommon for an organization to receive thousands or even millions of intrusion detection sensor alerts per day.”32 The April 2025 revision, SP 800-61r3, which “supersedes SP 800-61r2,” drops that sentence; its nearest line is a recommendation that “The volume of potentially adverse events to be analyzed is generally quite high, so organizations should rely on technical solutions that filter large event datasets down to a subset that is suitable for human viewing and analysis.”33 I take the filter as the instruction and the digest as one.

One vendor survey, labeled as one. Vectra AI’s 2023 survey, run by Sapio Research across 2,000 security analysts, reports “SOC teams receive an average of 4,484 alerts per day, and over two-thirds (67%) of them are ignored.”34 It is a vendor-commissioned opinion survey, and the 67 percent is what respondents said, not a measurement. I cite it for one point only: the failure scales down. Analysts say they ignore two-thirds of 4,484 alerts a day, and one reader with no team stopped reading at 74.

4. The redesign, as rules

The change went in at 13:50 on November 1, 18 minutes after my message, and a second change at 14:00 tightened one of its conditions.18 I wrote it as rules, each with the reason it exists.

Rule 1: one digest a day, and it is the heartbeat. The first monitor run after 07:00 local time sends one email covering the window since the previous digest.1835 The digest’s own description says what the email replaces: “One email a day … in place of an email per auto-block and per WAF-handled CRITICAL.”35 It opens with run health (runs recorded, runs started and finished, failures, critical emails sent and suppressed), then the auto-blocks with zone, reason, and network operator, every critical and what became of it, findings by severity and category, the paths probed, and the local scanner’s unresolved findings.12 The reason for the digest is the inbox. The reason for sending it daily is in the same description: “The digest is also the heartbeat: it goes out on a quiet day too”.35 An alert stream that only speaks when something happens cannot tell me the difference between a calm day and a dead monitor. A digest that always arrives can: silence means breakage, not calm. The program’s own notes record why I care: earlier in 2026 the monitor ran dead for months, and nothing noticed.35

Rule 2: blocks go to chat, not mail. The design notes give it four words: “Per block: Slack only.”3 A block is the edge acting on my behalf. In the SRE book’s terms it is logging: recorded, useful later, and nothing a human needs to act on.7 Chat keeps the live stream for the moments I want to watch one, and the digest keeps the record.

Rule 3: fold a critical when the edge already handled it. A critical still tries to block first; then the monitor decides whether it deserves an email.3 Two conditions fold it into the digest instead, checked in this order. The first: the finding’s own address, and every address in it eligible for automatic blocking, is already on the active block list, so the edge is stopping those addresses now.314 That check looks at no other address and runs before the sample is examined, so a flood folded by it can still hold events with no block or challenge recorded, from addresses below the bar for blocking; this branch says the responder’s targets are blocked, not that every event was mitigated. The second applies to a WAF flood, a burst of firewall events on one zone: it folds only when every event in the sample, bot or not, has a block or a challenge recorded, and the sample is complete. “A solved or bypassed challenge, log, allow and skip all count as unmitigated.”3 Cloudflare’s definitions are why those four count against the fold: after a challenge, “If successful, Cloudflare accepts the matched request”, and a log action only “Records matching requests in the Cloudflare Logs.”36 A persistent-scanner finding, which counts only events the edge blocked or challenged, folds by the WAF rule for that reason, or by the block-list rule when its address is already listed, as the November 2 one was.3 Anything else emails at once.

Rule 4: the outcome goes on the finding. The policy is that every critical from the cloud half carries one recorded outcome: folded because the address is on the block list, folded because the edge blocked or challenged every sampled event, emailed, suppressed (inside the limit of one email per site and category every two hours), or email failed.337 The record falls short of the policy. Of the criticals dated November 1 in UTC, 118 carry no outcome: 117 were raised before the field existed, and one cloud critical, at 14:13 PST, came after it; its run was one of the three in section 7 that ended about four minutes after starting, and it stopped during the automatic block, before an outcome was written. The local scanner’s criticals never carry one; the three of November 2 have none.3738 Since November 3 the monitor writes “pending” on a critical before the automatic block runs, so a run cut off midway leaves a visible state instead of a blank.14 The digest prints the outcome beside every cloud critical, under a heading that promises it: “Every CRITICAL and what became of it”.12 The outcome field is my mechanism, not Ewaschuk’s. His condition for a daily report is a person designated to triage it every day, and here that person is me; the field gives that triage something to check.30 With the outcome on each line, the digest answers the question I asked on November 1, routine or real, one critical at a time, though “emailed” or “folded” says where a critical went, not that anything was resolved.

Rule 5: the hourly volume alert stays. The notes again: “Hourly volume alert (responder): still immediate, at most once an hour”.3 A single block is routine; a sudden pile of them in one hour is the closest thing the cloud half has to a symptom, a change in what the internet is doing to me. It fired once on November 2.5

Rule 6: the local scanner’s criticals still email at once. The design notes keep them immediate because they are meant to be rare.3 I keep them for a second reason: nothing stands between my Mac and a local finding the way the edge stands between my sites and a scanner. A block list can stop an address; nothing outside the Mac stops a process on it. The cost of that rule shows up in section 7.

On the day the rules went live they cut the mail stream from 74 emails before 13:32 to seven logged in the rest of the day (six criticals and one hourly alert), plus the digest I sent by hand.52 The forecast I wrote that afternoon said “Under today’s traffic that would have been 0 immediate emails instead of 16.”1 It did not hold, and section 6 explains why.

5. The test case: November 2, 11:03 to 11:21

The first real test of the rules arrived the next morning: one address belonging to a Dutch hosting provider ran a vulnerability scanner across my site.12 Here is what the monitor wrote, verbatim from its log in local Pacific time, with the address replaced by “[the address]” and the lines in between left out.4

[2026-11-02 11:03:53] [INFO] New finding [critical]: Request flood from [the address] on blakecrosley.com
[2026-11-02 11:04:02] [RESPOND] [INFO] Block notification: Slack posted, email folded into the daily digest
[2026-11-02 11:04:03] [INFO] Critical alert folded into the daily digest (IP on the block ledger): Request flood from [the address] on blakecrosley.com
[2026-11-02 11:04:04] [INFO] New finding [critical]: Scanner from [the address] on blakecrosley.com
[2026-11-02 11:04:05] [RESPOND] [INFO] Skip: [the address] already has active real block in blakecrosley.com (dedup)
[2026-11-02 11:04:05] [INFO] Critical alert folded into the daily digest (IP on the block ledger): Scanner from [the address] on blakecrosley.com
[2026-11-02 11:09:22] [INFO] New finding [critical]: 726 WAF blocks in 5 minutes
[2026-11-02 11:09:23] [INFO] Critical alert folded into the daily digest (IP on the block ledger): 726 WAF blocks in 5 minutes
[2026-11-02 11:09:44] [INFO] New finding [critical]: Persistent scanner: [the address] blocked 743 times today
[2026-11-02 11:09:47] [RESPOND] [INFO] Block notification: Slack posted, email folded into the daily digest
[2026-11-02 11:09:47] [INFO] Critical alert folded into the daily digest (IP on the block ledger): Persistent scanner: [the address] blocked 743 times today

Minute by minute:

  • 11:03:53. The request-flood detector raises a critical against the address. Eight seconds later, at 11:04:01, the responder adds it to the block list, and at 11:04:02 the block notice goes to chat with its email folded into the digest.4
  • 11:04:03. The critical itself folds: the address is now on the block ledger, so the edge is already doing the work an email would ask me to do.4
  • 11:04:04. A second detector, the scanner check, raises its own critical for the same address. The responder’s block request is skipped as a duplicate, and the critical folds a second later.4
  • 11:09:22. The WAF-flood detector raises a third critical for the zone. Its offender is on the ledger, and it folds.411 The block-list fold is all that decides it: eight of the sampled events had no block or challenge recorded and the sample was capped, so the WAF fold alone would have emailed it.11
  • 11:09:44. The persistent-scanner check, which counts the events the edge blocked or challenged from one address across all my zones, raises the fourth, recorded on another of my sites: 743 blocked or challenged events from the address that day.12 The responder writes the address to the list a second time (a defect, below), posts to chat, and folds the critical at 11:09:47.411
  • 11:14:58 and 11:20:40. Two more WAF-flood criticals for the same zone; both fold the same way.11

Four criticals in 5 minutes 51 seconds, six by 11:20, all recorded on their findings as folded because the address was blocked.11 One address blocked, its list entry written twice because the duplicate check then worked per site (fixed on November 3), four further block requests skipped as duplicates.414 Emails: zero. The log records no email sent between 11:03 and 11:21; the one critical in that window that neither folded nor emailed, suppressed at 11:03:13, was a flood on a different site from a different address.4

The next digest carried it as two rows in the auto-block table (the first block, with its zone, reason, and network operator, and the persistent-scanner re-add) and six rows in the table of every critical, each marked “blocked” or “blocked earlier”.12 On my reading that is the right weight for a scanner the monitor blocked eight seconds after its first finding, eight minutes after the scan began: a few lines I can read with coffee, not a page.4

One thing the test case does not show, found on November 3. The same scanner was also posting to my site’s contact form, posts my app answered 200, at most 31 emails of their own, the subject of the companion post, and the monitor’s form-flood check raised no finding live; replayed over complete data for the five minutes to 11:03:23, the same check raises the finding, at high severity (a digest line, not an email or a block), from 39 posts, 23 of them refused.39 The live run had asked Cloudflare for a window ending the moment it asked, 37 seconds after the run started, before the newest events had fully arrived: it counted 1,914 requests from the address in that window, where the replay over complete data counts 2,143.439 That lag is measured on all of the address’s requests, not on the form posts the check counts; on my reading, the same lag kept those posts under the check live. And each run looked back exactly five minutes while runs started about 5.6 to 5.8 minutes apart, so part of every interval was never scanned.3917 The fix went in the same day: each window now starts where the previous one ended and ends 90 seconds before the run, unless the previous record is missing, under a minute old, or more than 15 minutes old, as after the Mac sleeps, in which case the run falls back to a plain five-minute look-back.39 Replaying the November 2 window through the changed code gives the same findings, so the change did not disturb detection on a fixed window. A replay cannot exercise the tiling or the lag, which act only on live runs; the live runs from November 3 on are their test.39

6. What still emails, and why

November 2 was the first full day under the new rules, and it sent 19 emails, not one: 14 criticals, one hourly volume alert, one digest, and three from the local scanner.5 No per-block email went out; 108 block notices folded into the digest instead, 177 criticals folded, and 37 more were suppressed by the two-hour limit, all counted by local day; the outcome counts later in this post go by UTC date, which is why those figures differ.5

Stacked bar chart of emails per local day by stream. October 31, a partial day from 22:31: 10 emails, 4 per-block and 6 critical. November 1: 82, made of 55 per-block, 22 critical, 3 hourly volume, 1 digest sent by hand, and 1 from the local scanner. November 2: 19, made of 14 critical, 1 hourly volume, 1 digest, and 3 from the local scanner, with no per-block email.

Emails per local day by stream; October 31 covers only the 89 minutes after the log starts.5

Every cloud critical that emailed after the 13:50 change through the end of November 2, 20 in all (six in the rest of November 1, fourteen on November 2), was a WAF flood on resumegeni.com or blakecrosley.com in which at least one event had no block or challenge recorded.13 The WAF fold declined each one correctly by its own rule: an unmitigated event was in the sample, so the monitor could not say the edge had handled it.3 In those floods no address had enough events to be eligible for automatic blocking, so the block-list fold had no address to check.13 The digest names the group the same way: “a CRITICAL that was neither blocked nor already handled by the WAF”.12

The subjects of those emails told a different story, and that was a defect. The flood summary read “N WAF blocks in 5 minutes,” but N counted every non-bot firewall event in the window, whatever action the edge took on it.13 Two examples from the findings, in local time:

When (PT) Site Subject said Blocked Challenged Unmitigated
Nov 1, 16:51 blakecrosley.com “104 WAF blocks in 5 minutes” 0 0 117 of 117 events
Nov 1, 22:45 resumegeni.com “108 WAF blocks in 5 minutes” 18 62 99 of 179 events

The first email claimed 104 blocks when the edge had blocked nothing.13 On my reading, a subject that says “blocks” on events with no block recorded is worse than noise: it tells the one reader that the system handled what it may not have. My first fix, on November 3, the day these counts were made, was itself wrong. It kept the non-bot count for N and counted blocked, challenged, and the rest over every event, so the 16:51 burst would have read 104 WAF events in 5 minutes: 0 blocked, 0 challenged, 117 through, two populations in one line. The review of this post caught it, and I fixed it again the same day.14 The flood summary, and with it the email subject, now counts one population, every sampled firewall event, and reads N WAF events in 5 minutes: B blocked, C challenged, U unmitigated (S from non-bot user agents); a JavaScript challenge counts as challenged, and the auto-block reason reads N WAF events from this address in 5 min.14 For the 16:51 burst that gives 117 WAF events in 5 minutes: 0 blocked, 0 challenged, 117 unmitigated (104 from non-bot user agents).1314 The WAF fold already keyed on unmitigated events, so the subject and the decision now count the same thing. The fix changes what the 14 subjects say. It does not change how many were sent, and 19 stands as the honest number for November 2.

The forecast I wrote on November 1 missed because its premise was wrong. It said the new rules “would have been 0 immediate emails instead of 16,” on the belief that for the seven criticals after 00:30 that morning “Cloudflare had already blocked every one of those requests.”1 The findings say otherwise for those too: the five the log shows emailed after 00:30 record at most one block and nine challenges each, against 76 to 89 events in their summaries.13 And in each of the six criticals that emailed that afternoon and evening, the edge had blocked or challenged only part of the events (none at all in the 16:51 burst), so even the 13:50 rule, which folded only when blocks and challenges covered every non-bot event, would have emailed every one.131 Ten minutes after the 13:50 change, a second change made the WAF fold count every event, bot user agent or not, and trust only a complete sample; it made the rule stricter, but it is not why the forecast missed.183 The forecast counted the blocks I believed in; the rule counted the events that were there.

What I do not know is whether those 14, the November 2 criticals that emailed, deserved an email. The findings record how many events were unmitigated, meaning no block or challenge was recorded for them, but not which actions they carried: a log rule, a skip, an allow, or a challenge a client solved.13 Nor is an unmitigated event a request that reached my site. Cloudflare says “A single HTTP request can generate one or more security events when it triggers security features,” and lists log and skip as actions that do not end rule evaluation the way a block does, so on my reading another rule can still act on the same request.2436 Cloudflare’s GraphQL tutorial shows the action field and sample values like "log" and "block", but it does not list every value the field can take.23 Cloudflare’s Security Events dashboard samples too: “For large volumes of traffic, Cloudflare uses sampling to return results faster.”24 Until the monitor records the actions behind each unmitigated event and ties events to requests, I cannot say which of those emails described traffic the edge let through, and the answer decides whether they deserve an email.

The three local emails on November 2 are the other open item. They report a “Browser -> shell chain” at 13:50 in which a shell spawned curl, and they sit unresolved in the findings.40 I have not verified whether they were the agent tooling I was running that afternoon or something else, so I do not count them as false alarms.

7. Two false signals

The crypto miner that was Spotlight

At 10:03 on November 1 the local scanner raised a critical, “High CPU unsigned process - possible crypto miner,” against spotlightknowledged, Apple’s own Spotlight daemon, at 74.9 percent CPU, and two seconds later logged “Critical email sent.”15 The bug was mine. The check looked for the phrase “valid on disk” in the output of the code-signing display command, codesign -d. The scanner’s notes now record the flaw in one clause: the phrase is one “which only codesign -v prints, so every process read as unsigned”.25 A skip list had hidden the flaw until an Apple framework daemon spiked, and my log for that day says so.1 The finding now carries its resolution: “false positive, Apple platform binary”.41

It cost one email, the only local email of November 1.5 The 16 local criticals before it, on October 31 and November 1, flagged Xcode, Homebrew’s Python, my own app builds, hook changes, version-manager shims on the path, an AI-worm pattern that matched another project’s agent instructions, and an event-tap check that matched its own query; they produced 15 send attempts, and every one failed.5251915 The mail service sits behind Cloudflare, which answered Python’s default user agent with error 1010 before the key was read; I reproduced it with a deliberately bad key.19 Cloudflare’s page for the error names that client directly, in its note on Pages and Workers hosts: “a User-Agent header that Browser Integrity Check treats as a non-browser client (for example, the default Python urllib user agent).”42 A broken sender saved me from 15 emails about my own tools and my own changes.15 The scanner’s notes put the larger lesson plainly: “most of the CRITICALs it raised in its first live days were its own noise.”25 I keep the local criticals immediate anyway, and I fix each false one at its check rather than muting the stream.

RUNS SHORT 162/207

The first scheduled digest, on the morning of November 2, carried the subject “Mon Nov 2: 71 blocks on 15 zones, 11 criticals emailed, RUNS SHORT 162/207”.16 The digest’s rule was simple arithmetic: the expected run count is the window’s minutes divided by five, and the subject flags a shortfall.35 The window ran from 13:48 on November 1 to 07:05 on November 2, 1,036.5 minutes, so 207 five-minute slots; the log recorded 162 runs.17

My log for November 2 explained the gap in one line: “The RUNS SHORT line is the Mac asleep,” with no fault in the monitor.43 That line is right that the monitor did not fail (no run on November 2 was left unfinished) and wrong about the cause, on November 2 as well as over the window the number described. The run-gap analysis splits the 45 missing slots, approximately, into about 25 lost to the cadence, about 16 to sleep, about one more in a long gap after a run that ended early, and three runs that never recorded themselves.17

Cause Slots, approximate Evidence
The real cadence 25 160 ordinary gaps between runs, median 5.60 minutes, never under 5.35; they fill 926.0 minutes, 185 five-minute slots
Sleep 16 three gaps over 10 minutes, 93.0 minutes in all, inside the power log’s idle-sleep periods, with runs resuming during brief maintenance wakes
A long gap after an early stop 1 the fourth gap over 10 minutes, 10.0 minutes after the 14:19 run that ended early
Runs that never recorded themselves 3 165 run starts against 162 recorded runs
Behind those three three runs ended about four minutes after starting with no ending line logged, by the timing the 240-second watchdog, all on November 1: twice in the afternoon and once late that evening

The power log shows idle sleep at 23:21:30 and full wake at 00:30:05, and again idle sleep at 06:01:20 and full wake at 06:30:28.44 The three runs started at 14:09, 14:19, and 23:10 on November 1; each ended about four minutes later with no ending line logged, which on my reading is the 240-second watchdog. A fourth run ended 28 seconds after starting with no visible cause.1738 On the November 2 calendar day, 242 runs started against 288 five-minute slots, 46 short, and none was left unfinished; only two gaps passed 10 minutes, 35.8 minutes in all, so on that day too most of the shortfall was the cadence, not sleep.17

The wrong assumption was mine, in the digest: a scheduler set to five minutes delivered a run about every 5.6 to 5.8 minutes even while the Mac was awake, so the five-minute arithmetic would call a healthy day short.17 I have not established why the awake cadence runs slow. I replaced the arithmetic on November 3. Run health now reports the runs recorded, the median minutes between them, and the longest gap with its time, and the subject flags only a gap longer than 20 minutes. That morning’s preview read “about one every 5.6 min”, and its subject ended “RUN GAP 31 min at 05:56”.14 A gap is a symptom I can act on: the Mac slept, or the monitor stopped. A ratio against a cadence the scheduler never delivered was neither.

The rules I kept

  1. Count the mail before you change it, by stream, with a stated rule. My table and the log count agreed on 74 only because I wrote down what counted as an email first.12
  2. Log “sent” only when the mail service says yes. For 22 of my 74 emails I can prove an attempt and nothing more.26
  3. A block the edge already made is a log line, not an alert. The SRE book’s three outputs put it there, and moving 55 per-block emails to chat lost nothing I acted on.75
  4. Send a daily digest even when nothing happened. A digest that always arrives is the heartbeat; its absence is the alert.35
  5. Fold a critical only on a condition the edge proves. Mine are the finding’s blockable addresses already on the block list and a complete sample in which every event has a block or challenge recorded, and the second one is why 14 still emailed on November 2.313
  6. Write the outcome on each cloud critical. A daily report nobody is accountable for is, in Ewaschuk’s words, “less-spammy email alerts”; the outcome on each line is what my daily triage checks.3012
  7. Make the subject say what the edge did. “104 WAF blocks” on a burst with zero blocks was my worst line of the week.13
  8. Report gaps, not ratios against a schedule. A five-minute timer delivered a run about every 5.6 minutes, so 162/207 looked like an outage and was mostly a clock.17
  9. Fix a false alarm at its check, not at the inbox. One misread command output made Apple’s Spotlight daemon a crypto miner; the fix went into the check.25

What could change

  • If the unmitigated events turn out to be solved challenges, that alone would not make the WAF-flood criticals that still email noise. When a client passes a challenge, “Cloudflare accepts the matched request”: the request proceeds, and that does not show the request was harmless or that nothing needs a response. Folding those criticals would take request evidence tied to the same events showing that nothing in them needed a person, and challenge completion alone is not that evidence. If they turn out to be log or skip rules, the question is whether those rules do what I meant, and each email is worth reading until I know. The monitor does not record which yet.1336
  • If a day’s critical emails climb back toward the dozens, the two-hour limit per site and category is doing the work that a rule should do, and the rule needs another look.3
  • If the digest stops arriving, something between the monitor and my inbox has stopped: the monitor, the Mac it runs on, the sender, or the delivery path, since an accepted send is not yet a delivered one. That is the one silence the design treats as a prompt to look.3520
  • If the local scanner raises a critical I cannot explain from my own tooling, the immediate email was the right call; the three unresolved shell-chain findings of November 2 are the open test of that.40
  • If I add a reader, pages and tickets stop being the same person, and the SRE book’s split between alerts and tickets would need a ticket queue, not my inbox.7

Key Takeaways

If you run a monitor for yourself

  • I stopped reading somewhere short of 74 emails in half a day, a count my inbox reached by early afternoon.1
  • A daily digest that arrives on quiet days too is a heartbeat for free; a monitor that only speaks on events cannot tell you it died.35
  • Your logs may say “sent” for mail that never left. Check the code before you trust the count.619

If you build one

  • Act first, then decide whether to tell: block, then fold the alert if the block list or the edge already covers it.3
  • Record each critical’s outcome on the finding and print it in the digest, so the person Ewaschuk says must triage a daily report has something to check; on my monitor that person is me.3012
  • Name events by what the edge recorded for them (blocked, challenged, unmitigated), count one population in one line, and never name them by what you hoped the edge did.1314
  • Measure the scheduler you have. Mine ran every 5.6 minutes on a five-minute timer.17

If you are the one reading the inbox

  • Ask of every alert whether it is “urgent, important, actionable, and real”; anything that fails belongs in a digest or a log.29
  • My first-day forecast missed: it said zero immediate criticals, and the next day sent 14.15

FAQ

What is alert fatigue?

Alert fatigue is what happens to the people on the receiving end of too many alarms that need nothing from them: they become, in the Joint Commission’s words, “desensitized or immune to the sounds,” and start turning alarms down or off.10 Google’s SRE book describes the same thing in operations, where people “second-guess, skim, or even ignore incoming alerts, sometimes even ignoring a “real” page that’s masked by the noise.”27 My own case took one day: 74 emails by 13:32, none of which, on my reading that afternoon, asked me to act.1

How many security alerts per day is too many?

For a person, the SRE chapter’s honest answer is a few: “I can only react with a sense of urgency a few times a day before I become fatigued.”8 Vectra’s vendor survey reports teams receiving an average of 4,484 alerts a day and, by their own account, ignoring 67 percent of them.34 For my one-person monitor the working answer is one digest a day plus the alerts that need a human now; on November 2 that was still 19, and I treat the 14 WAF-flood criticals among them as an open question rather than a settled cost.513

Should security alerts be a daily digest or real time?

Both, split by what the human must do. Anything the monitor counts as already handled (an address it has put on the block list, or a flood in which every sampled event was blocked or challenged) goes into the digest; the rest goes out at once, within the two-hour limit.3 Ewaschuk allows that a daily report “can work too,” with one condition: without accountability it “amounts to less-spammy email alerts,” and he wants someone “designated to triage these every day”.30 On my monitor that is me, and my digest prints what became of every cloud critical so the triage has something to check.12

What is a heartbeat alert?

A heartbeat is a message that arrives on a schedule whether or not anything happened, so that its absence means something broke. My daily digest is the heartbeat: “it goes out on a quiet day too,” so a monitor that has stopped running shows up as a missing email.35 So does a failed send or a lost delivery, so a missing digest is a prompt to check the monitor, the host it runs on, the sender, and the delivery path.20 Its run-health section also reports the longest gap between runs and flags a gap over 20 minutes in the subject.14

Why do Cloudflare WAF alerts flood my email?

Mine flooded because each block and each critical became its own message, and the internet scans every public zone all day.5 Cloudflare’s own Alerts, “Monitors that you define yourself, and Platform Notifications,” can be delivered by email and are set up in its dashboard; a monitor you build yourself against its API, like mine, is neither, and inherits whatever per-event mailing you wrote into it.26 Check what an alert counts, too: Cloudflare’s actions include log and skip, which do not end rule evaluation the way a block does, and a challenge that a client solves ends with Cloudflare accepting the request, so “WAF events” and “WAF blocks” are different numbers.36 My subjects confused the two until November 3.1314

Does a 2xx from the email API mean the alert was delivered?

No. Resend’s email.sent event “Occurs whenever the API request was successful. Resend will attempt to deliver the message to the recipient’s mail server,” and delivery is a separate event.20 A 2xx means the service accepted the message. Log the status code, and treat delivery as a different question.6

Related on this site: Why Spam Beat My Rate-Limited Contact Form follows the same November 2 scanner from the contact form’s side; What I Run Before I Sleep is the nightly production check, the other routine that runs while I am not looking; What Actually Breaks When You Run AI Agents Unsupervised names the failure modes of unattended agent sessions, each with a detection signal; Every Hook Is a Scar traces each of my agent hooks to the failure that produced it, the same habit as fixing a false alarm at its check; Claude Code Auto Mode Is Not a Security Boundary argues that the wall around an agent matters more than its classifier; and AI Agent Observability makes the case for seeing what agents do to disk, CPU, and network.

Sources

All times in the author’s own data are local Pacific time unless marked UTC; November 1 fell back from PDT to PST at 02:00. Public pages were fetched on November 3, 2026, at the UTC times given.


  1. Author’s operations log (Captain’s Log) for November 1, 2026: the 13:32 PT message; the table of 74 emails since midnight by stream (55 per-block, 16 critical, 2 hourly volume, 1 local); the 13:50 fold rule (blocks and challenges covering every non-bot event); the critical split as “9 before the fold went live at 00:30 PT; the 7 after” with the belief that “Cloudflare had already blocked every one of those requests”; “Yesterday had 10”; the first digest sent by hand at 13:48 PT; the forecast “Under today’s traffic that would have been 0 immediate emails instead of 16”; and the note that the skip list had hidden the code-signing bug. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  2. Author’s data: the email count derived by script from the monitor’s logs, cut at 13:32 PT on November 1: 74 (55 per-block, 16 critical, 2 hourly volume, 1 local), of which 22 (10 per-block, 11 critical, 1 hourly) came from the senders that logged “sent” unconditionally; 81 for the whole local day. The same script’s line listing places 11 critical “sent” lines before 00:30 and 5 after, and the first critical folded into a block notice at 00:54:43. ↩↩↩↩↩↩↩↩↩

  3. Author’s design notes for the monitor, notifications section, as of November 3, 2026: “Every one described routine mass-scanning the edge had already stopped”; “Per block: Slack only”; the two fold conditions and their order; “A solved or bypassed challenge, log, allow and skip all count as unmitigated”; the hourly volume alert kept immediate; the local scanner’s criticals kept immediate because they are meant to be rare. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  4. Author’s data: the monitor’s log for November 2, 2026, 11:03 to 11:21 PT: the lines quoted in section 5 (address replaced), the block-list writes at 11:04:01 and 11:09:46 with a seven-day expiry, four duplicate block requests skipped, the chat postings, and no email line in the window other than a suppressed flood on another site at 11:03:13; the run that raised the first critical started at 11:02:46 and its detector window ended at 11:03:23, 37 seconds later, and its block reason counted 1,914 requests from the address in that window. The scan’s first requests reached the site at 10:56 PT, eight minutes before the block (the author’s operations log for November 2 and Cloudflare’s per-minute request counts for the address). ↩↩↩↩↩↩↩↩↩↩↩↩↩

  5. Author’s data: the email count derived by script from the monitor’s six logs for October 31 to November 2, 2026 (the cloud half and the local scanner), one email per line recording a hand-off to the mail service. Per local day: October 31 (log starts 22:31), 10 (4 per-block, 6 critical, all from the senders that logged “sent” unconditionally; 3 criticals suppressed by the two-hour limit; 13 local sends failed); November 1, 81 logged (55 per-block, 22 critical, 3 hourly volume, 1 local; 2 local sends failed) plus one digest sent by hand; November 2, 19 (14 critical, 1 hourly volume, 1 digest, 3 local). Not emailed on November 2: 108 block notices and 177 criticals folded into the digest, 37 criticals suppressed. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  6. Author’s code: the monitor’s senders as armed on October 31, 2026, which discarded the mail request’s answer and then logged “sent” unconditionally, and the change of November 1, 01:04 PDT, before the clocks fell back, after which the cloud half’s senders log the mail service’s HTTP status and say “sent” only on a 2xx; the local scanner’s sender writes a bare “Critical email sent” line, and only when the request raised no error. ↩↩↩↩↩↩

  7. Benjamin Treynor Sloss, “Introduction,” chapter 1 of Site Reliability Engineering: How Google Runs Production Systems (Google, published by O’Reilly Media), fetched 14:19:33Z: the three kinds of valid monitoring output (alerts, tickets, logging), quoted, and the passage calling email-and-decide alerting “fundamentally flawed.” ↩↩↩↩↩↩

  8. Rob Ewaschuk, “Monitoring Distributed Systems,” chapter 6 of Site Reliability Engineering (Google, O’Reilly Media), fetched 14:18:54Z: “Every page should be actionable,” the urgency line, and the robotic-response line, quoted. ↩↩↩

  9. Ewaschuk, “Monitoring Distributed Systems,” fetched 14:18:54Z: the definition of an alert and footnote 22 on email alerts (“alert spam”), quoted. ↩↩

  10. The Joint Commission, “Medical device alarm safety in hospitals,” Sentinel Event Alert Issue 50, April 8, 2013, read from the Wayback Machine’s capture of November 13, 2024, fetched 14:21:49Z (the publisher’s own URL answered 403): alarm signals per patient per day; the 85 to 99 percent estimate, which the alert attributes to AAMI and which covers alarms that need no clinical intervention without separating false ones; the desensitization and turning-down passages; 98 alarm-related events from January 2009 to June 2012, 80 resulting in death; and “the most common contributing factor,” quoted from the alert’s list of factors beyond those reported to its Sentinel Event database, which is separate from the database’s own list of reported factors. ↩↩↩↩↩

  11. Author’s data: the six critical findings for the November 2 scanner in the monitor’s findings file (11:03:53 request flood, 11:04:04 scanner, 11:09:22 WAF flood, 11:09:44 persistent scanner, 11:14:58 and 11:20:40 WAF flood), every one recorded as folded because the address was on the block list; the 11:09:22 WAF flood also records eight sampled events with no block or challenge recorded, and a firewall-event count equal to the monitor’s sample limit, so its sample was capped. ↩↩↩↩↩↩

  12. Author’s data: the next digest as the digest program’s preview mode rendered it on November 3, 2026 (window Monday, November 2, 07:05 to Tuesday, November 3, 06:17 PST; preview sends nothing): its sections, the local scanner’s 10-minute tiers, the account-wide block list enforced on every zone for seven days, the auto-block table with the network operator (a Dutch hosting provider for the November 2 scanner), the “Criticals that emailed you” heading text, and the “Every CRITICAL and what became of it” table. ↩↩↩↩↩↩↩↩↩↩↩↩

  13. Author’s data: every critical recorded as emailed from the 13:50 PST change on November 1, when the outcome field begins, through the end of November 2 PST, 20 in all (15 dated November 1 and 2 UTC and five dated November 3 UTC, sent at 16:42, 17:58, 20:34, 21:07 and 23:08 PST), from the findings file with the details that decided each: all WAF floods on resumegeni.com or blakecrosley.com with events that had no block or challenge recorded; the six of November 1 with the non-bot events the edge blocked or challenged (the findings’ acted count) below the event count (5 of 83, 95 of 153, 0 of 104, 67 of 128, 54 of 135, 80 of 108); two of November 2 with none blocked (05:55 PST, 26 challenged; 12:06 PST, 204 challenged); for all 20, the monitor’s log shows no single address with enough events for a block; the two rows tabulated in section 6 (2026-11-02T00:51:04Z and 06:45:59Z); the summary text counting every non-bot firewall event as a “WAF block”; for the 16:51 burst, 104 non-bot events and 117 events in all, none blocked or challenged; and, for the five criticals the log shows emailed between 00:30 and 13:32 PT on November 1, the findings’ blocked and challenged counts (0 and 0 against 79 events, 0 and 9 against 81, 1 and 0 against 89, 1 and 9 against 84, 0 and 5 against 76); the findings do not record which actions those events carried. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  14. Author’s code and data, November 3, 2026: the monitor’s WAF-flood summary template, first rewritten that morning as N WAF events in 5 minutes: B blocked, C challenged, U through with N the non-bot count, then, after the review of this post, as N WAF events in 5 minutes: B blocked, C challenged, U unmitigated (S from non-bot user agents) over every sampled event, JavaScript challenges counted as challenged; its auto-block reason N WAF events from this address in 5 min; the “pending” outcome written on a critical before the automatic block runs, which the digest prints as a run cut off before the alert path finished; the block-list fold’s address set (the finding’s own address and the addresses eligible for automatic blocking) and its place before the WAF fold; the auto-block duplicate check, which until then worked per site and so wrote the November 2 address to the list twice, made per address that day (the monitor’s design notes for that version and the operations log for November 3); the digest’s run-health rule (runs recorded, median minutes between runs, longest gap with its time, a subject flag only for a gap over 20 minutes); and that morning’s preview, which read “about one every 5.6 min” and “RUN GAP 31 min at 05:56”. ↩↩↩↩↩↩↩↩↩↩↩

  15. Author’s data: the local scanner’s log for November 1, 2026: the 10:03:04 critical finding against Apple’s spotlightknowledged at 74.9 percent CPU and the 10:03:06 “Critical email sent” line; the two earlier criticals that day (00:48:55, the path shims), each followed by a failed send; and the October 31 log’s 14 criticals, among them the AI-worm pattern at 22:52:57 and five “Claude hook modified” findings at 22:41, four of them followed by a failed send (the hooks had been changed on October 24, as the October 31 operations log records). ↩↩↩↩

  16. Author’s data: the digest program’s state record after the November 2 digest: subject “Mon Nov 2: 71 blocks on 15 zones, 11 criticals emailed, RUNS SHORT 162/207,” window 2026-11-01T21:48:49Z to 2026-11-02T15:05:17Z, 162 runs; the monitor’s log records the send at 07:05:19 PST. ↩↩

  17. Author’s data: a run-gap analysis by script over the monitor’s logs for November 1 and 2, 2026. For the November 2 digest’s window (1,036.5 minutes, 207 five-minute slots): 165 run starts, 4 with no “run complete” before the next start; 160 ordinary gaps, median 5.60 minutes, mean 5.79, minimum 5.35, summing to 926.0 minutes (185 slots); 4 gaps over 10 minutes, 103.0 minutes in all. For the November 2 calendar day (midnight to midnight, 1,440 minutes, 288 five-minute slots): 242 starts, 46 fewer than the slots, none unfinished; 239 ordinary gaps, median 5.62 minutes, summing to 1,374.7 minutes (about 275 slots); 2 gaps over 10 minutes, 35.8 minutes in all. The decomposition is approximate arithmetic from these figures: about 25.2 cadence slots (the 160 ordinary gaps span 185.2 five-minute slots and hold 160 runs), about 15.6 slots in the three sleep gaps (93.0 minutes, 18.6 slots, three runs), about 1.0 slot in the 10.0-minute gap after the 14:19 run, and 3 unrecorded runs, about 45 in all. ↩↩↩↩↩↩↩↩↩

  18. Author’s data: the monitor’s configuration history, read from its repository: arming on October 31, 22:44 PDT (an email per critical and per session end); November 1, 00:46 PDT (a critical folded into the block email); November 1, 13:50 PST (digest on at 07:00, per-block email off, edge-handled criticals folded); November 1, 14:00 PST (no configuration change; the WAF fold’s code changed to count every event and require a complete sample). ↩↩↩↩↩↩↩

  19. Author’s operations log (Captain’s Log) for October 31, 2026: the local scanner’s sends failing on Cloudflare’s error 1010 for Python’s default user agent, reproduced with a bad key; the count of 13 failed sends against 14 criticals that night; the cloud emails that “most likely were” delivered. ↩↩↩↩

  20. Resend, “Event Types,” webhook documentation, fetched 14:23:37Z: the email.sent definition, quoted, and email.delivered as a separate event. ↩↩↩↩

  21. Resend, “Send Email,” API reference, fetched 14:18:59Z: the response body is an id. ↩

  22. Author’s data: November 1, 2026, by local hour, from the same script, with the 01:00 hour kept apart for PDT and PST: emails per hour (22 in the midnight hour, all unverified), alerts folded or suppressed per hour, the last per-block email at 13:25:57, and the first folded per-block notice at 14:20:35. ↩↩↩↩

  23. Cloudflare, “Querying Firewall Events with GraphQL,” Analytics docs, fetched 14:19:01Z: the query’s fields, including action, and sample output with "log" and "block"; the page does not list every action value. ↩↩

  24. Cloudflare, “Security Events,” WAF docs, fetched 14:23:10Z: the definition of security events, the sentence that one request can generate several events, and the sampling sentence, quoted. ↩↩↩

  25. Author’s design notes for the local scanner, “Check fixes and false positives”: “most of the CRITICALs it raised in its first live days were its own noise,” the table of false positives (Homebrew Python, Xcode, the author’s app builds, the event-tap self-match, version-manager shims, the AI-worm pattern on another project’s agent instructions), and the code-signing entry, quoted; the scanner emails its own criticals. ↩↩↩↩↩

  26. Cloudflare, “Cloudflare Alerts,” Notifications docs, fetched 14:19:00Z: the two categories of alerts, quoted; Monitors as scheduled SQL API queries and Platform Notifications as pre-configured product alerts, both created from the dashboard, with email delivery on all plans. ↩↩

  27. Ewaschuk, “Monitoring Distributed Systems,” fetched 14:18:54Z: the passage on pages that occur too frequently, quoted. ↩↩

  28. Ewaschuk, “Monitoring Distributed Systems,” fetched 14:18:54Z: the case study of a team triaging alerts, and footnote 24 pointing to cardiac alarm management “for an example of alert fatigue in another context,” quoted. ↩↩

  29. Rob Ewaschuk, “My Philosophy on Alerting,” Google Doc, read from its HTML export, fetched 14:18:55Z: the doc’s note that its ideas form the foundation for the SRE book chapter, “Pages should be urgent, important, actionable, and real,” and the cause-based alerts passage and section title, quoted. ↩↩↩

  30. Ewaschuk, “My Philosophy on Alerting,” fetched 14:18:55Z: the daily report passage, its accountability caveat, and its call for a person designated to triage the report every day, quoted. ↩↩↩↩↩↩

  31. Steven Thurgood, with Jess Frame, Anthony Lenton, Carmela Quinito, Anton Tolchanov, and Nejc Trdin, “Alerting on SLOs,” chapter 5 of The Site Reliability Workbook (Google, O’Reilly Media), fetched 14:18:55Z: the definitions of precision, recall, and reset time, quoted. ↩↩

  32. NIST, SP 800-61 Revision 2, Computer Security Incident Handling Guide (2012, superseded), fetched 14:18:58Z: the “thousands or even millions” sentence, quoted. ↩

  33. NIST, SP 800-61 Revision 3 (April 2025), fetched 14:18:57Z: its statement that it supersedes r2, and recommendation R1 under Adverse Event Analysis, quoted. ↩

  34. Vectra AI, “2023 State of Threat Detection” (vendor survey; March to April 2023, commissioned by Vectra, fieldwork by Sapio Research, 2,000 security analysts at organizations over 1,000 employees in ten markets), fetched 14:22:49Z: 4,484 alerts a day and 67 percent ignored, as respondents reported. ↩↩

  35. Author’s code: the digest program’s description (“One email a day … in place of an email per auto-block and per WAF-handled CRITICAL”; “The digest is also the heartbeat: it goes out on a quiet day too”; the monitor’s months-long dead stretch earlier in 2026) and, as of November 2, its expected-run rule of window minutes divided by five. ↩↩↩↩↩↩↩↩↩

  36. Cloudflare, “Actions reference,” Ruleset Engine docs, fetched 14:19:01Z: the block, challenge, skip, and log actions, the challenge and log definitions, quoted, and skip and log listed as non-terminating. ↩↩↩↩

  37. Author’s data: critical findings by recorded outcome and category from the monitor’s findings file, per UTC date. November 1: 129, of which 118 carry no outcome: 117 raised before outcomes were recorded (among them the local scanner’s process, hook, and supply-chain categories, which never carry one), and one cloud scanner critical at 14:13 PST, after the change, whose run’s last logged line is the automatic block it triggered (the monitor’s log for November 1). November 2: 236 (160 folded because the address was blocked, 1 folded because the WAF handled it, 58 suppressed, 14 emailed, 3 local with no outcome). ↩↩

  38. Author’s data: the monitor’s log for November 1, 2026: three runs (started 14:09:49, 14:19:46, and 23:10:31) whose last lines fall about four minutes after the start, with no watchdog, timeout, or kill line logged, which the author reads as the monitor’s 240-second watchdog (the code kills a run at 240 seconds), and a run started 14:36:06 that ends at 14:36:34 without “run complete.” ↩↩

  39. Author’s replay, November 3, 2026, of the monitor’s own form-flood check on complete Cloudflare data for 18:58:23 to 19:03:23 UTC on November 2 (10:58:23 to 11:03:23 PST), which raises the finding the live run missed, at high severity (which the monitor sends to the digest, not to email or the block list), from 39 posts with 23 refused, and counts 2,143 requests from the address where the live run counted 1,914; the two causes as read in the monitor’s code that day (each run’s window ended the moment the detector asked, before Cloudflare’s newest data had fully arrived, and five-minute look-backs on runs started more than five minutes apart left part of every interval unscanned) and the fix made that day (live windows tile from a record of the previous window’s end and end 90 seconds early; a missing record, one under 60 seconds old, or one over 15 minutes old falls back to a five-minute look-back; replays never read the record), with a replay of the same window through the changed code giving identical findings, which shows the change left detection on a fixed window alone and does not exercise the tiling or the lag; the scanner’s contact-form posts as recorded in the author’s operations log for November 2. The replay through the November 3 code is saved with the rest of this post’s evidence: its exact commands, the window it read, and the detector’s full output for the run window and for the form minutes 18:58 to 19:05 UTC, run at 15:55:04 UTC on November 3. The first replay, made earlier on November 3 through the code as it stood before the fix, is recorded in my program notes with the same counts; its raw output was not saved. ↩↩↩↩↩

  40. Author’s data: the local scanner’s log for November 2, 2026, and the November 3 digest preview: three critical “Browser -> shell chain” findings at 13:50 PST, a shell spawning curl, unresolved in the findings file. ↩↩

  41. Author’s data: the resolution recorded on the November 1 spotlightknowledged finding in the findings file, quoted. ↩

  42. Cloudflare, “Error 1010,” Support docs, fetched 14:19:00Z: the error’s definition and the Python urllib example, quoted; the page gives that example for requests to *.pages.dev and *.workers.dev hostnames and Pages custom domains. ↩

  43. Author’s operations log (Captain’s Log) for November 2, 2026: the RUNS SHORT line, quoted. ↩

  44. Author’s data: the Mac’s power log (Sleep and Wake lines from November 1, 13:40 PST): idle sleep 23:21:30 and full wake 00:30:05; idle sleep 06:01:20 and full wake 06:30:28. ↩

Related Posts

AI Agent Observability: Monitoring What You Can't See

AI agents consume disk, CPU, and network with zero operator visibility. Three observability layers close the gap before …

24 min read

Overnight: Why My Site Is Busier at 3am Than at 3pm

Googlebot crawls 21,000 pages overnight, Bingbot crawls 10,000, and my nightcheck grinds through 15,000. Bot traffic exc…

8 min read

The Ralph Loop: How I Run Autonomous AI Agents Overnight

I built an autonomous agent system with stop hooks, spawn budgets, and filesystem memory. Here are the failures and what…

11 min read