Build1 publisher3 min readPublished
The uptime monitor was the outage: 288 scheduled runs a day became 15
A GitHub Actions health check decayed to about 5 percent of its configured rate over seven months. The docs warn that queued scheduled jobs may be dropped, and dropped means nothing tells you.
The Engineer · Build desk
Drafted by a language model from the sources cited here and checked against its claim ledger before publication. How we use AISend a correction
What happened
- The health-check.yml workflow was configured with cron '*/5 * * * *', which the author calculates as 288 runs a day.
- The workflow curls /health on textstack.app and textstack.dev, then runs two smoke checks (book listing and search); if any fails, the run goes red and the author gets a notification.
- The workflow was created on 6 January 2026.
- The number of runs went down every single month from January to July.
- In July the health check ran 15 times a day, which the author describes as one check every 96 minutes instead of every 5.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
A health check configured with `cron: '*/5 * * * *'` should fire 288 times a day; in July it fired 15 times, which works out to one check every 96 minutes [1][5]. The developer who wrote it, publishing on dev.to about his TextStack repo, says he did not notice for seven months [7], and that is the part worth sitting with: the job whose entire purpose was to prove the site was alive had no mechanism for reporting its own absence.
The workflow itself is unremarkable and sensible. It curls `/health` on textstack.app and textstack.dev, then runs two smoke checks against book listing and search, and a failure turns the run red and sends a notification [2]. Notification on failure is the design flaw. A run that never starts is not a failure, so it produces no red mark and no message [2].
The decay was not an event. Created on 6 January 2026, the workflow lost ground every month from January through July [3][4], and the author notes it never hit the configured rate even in month one, landing at roughly a third [6] - call it about 96 runs a day, one check every 15 minutes or so [6]. By July, 15 of 288 is around 5.2 percent of the requested cadence [1].
The author's first read was that the 6 August GitHub Actions incident had blinded him. That incident opened at 15:22 UTC and was fully resolved at 02:04 UTC the next day, close to 11 hours, with 71 percent of workflow runs failing on infrastructure errors at peak [8][9]. His health check ran four times that day, two of which failed, and the largest gap was 5 hours 25 minutes from 18:33 to 23:59, sitting inside the incident window [11][12]. Four runs against 288 is about 1.4 percent of the schedule [4], and that single gap swallowed roughly 65 checks that should have happened [2].
Then he checked the same day before the incident and found a 3 hour 22 minute gap from 00:08 to 03:30, about 40 missed checks, hours before anything broke [13][3]. On 3, 4 and 5 August the workflow ran 12, 13 and 13 times, an average near 12.7 a day or about 4.4 percent of schedule [14][5]. The outage was the day he looked, not the cause.
The documented cause is blunter. GitHub's docs on the `schedule` event say scheduled events can be delayed under high Actions load, and that if load is high enough some queued jobs may be dropped [15]. Dropped, not deferred. The author lays out GitHub's own load figures - commits moving from about 1 billion across all of 2025 to 275 million per week by April 2026, Actions compute reaching 2.1 billion minutes in a single week, AI-agent pull requests going from about 4 million in September 2025 to more than 17 million in March 2026 [16][17][18] - and then explicitly declines to claim causation, because one repo cannot prove two curves are linked [19].
The control case is in the same repo. A daily database backup on `cron: '0 3 * * *'` with `runs-on: self-hosted` logged 224 runs since January with zero missing days, plus a couple of manual triggers in May and June [20]. One scheduled workflow ran at 5 percent, the other at 100 percent [21]. The author refuses to pin that on the self-hosted runner alone, since the two jobs also differ by a factor of 288 in how often they ask the queue for anything, and high-frequency schedules are what a queue sheds first [22].
Watch whether the run counts recover now that they are being counted, and whether anyone with more than one repo can separate the runner type from the request rate [22]. The transferable check is cheaper: count what your scheduler actually did, not what its config says it should do.