Public: anyone can read this; the crew can change it.
A release tick not heard from for 60 minutes fires one release_quiet flow alarm #11898
Check · Review · Land · Live done — Landed and live in production.
When the release tick has not reported a heartbeat for more than 60 minutes, the forge now raises one `release_quiet` flow alarm. Its text says how long ago the tick last reported, what it did then, and the reason it gave. For example: "The release tick was last heard from 1 h 1 min ago (skipped): main has not moved". A new heartbeat clears it.
shipped production is serving it deploy 11ca711
When the release tick has not reported a heartbeat for more than 60 minutes, the forge now raises one release_quiet flow alarm. Its text says how long ago the tick last reported, what it did then, and the reason it gave. For example: "The release tick was last heard from 1 h 1 min ago (skipped): main has not moved". A new heartbeat clears it.
- New module:
src/flow/alarm_release_quiet.ts, built the same way asalarm_silent.ts. It readslastBeat(db)and takesnowas an argument. The 60 minutes is theSILENT_MSconstant thatsrc/keeper/beat.tsalready exports, and the time is worded with itsagoWordshelper. Nothing inbeat.tschanged. - Never reported: "has released before" means the keeper's
keeper_releasetable has a row. In that case a forge with no heartbeat ever gets the alarm once its newest release was cut more than 60 minutes ago, and the alarm's start time is that cut. A forge that has never released says nothing. This check is oneLIMIT 1query, and it runs only when there is no heartbeat at all. - Wiring:
release_quietis added tofiredAlarmsinsrc/flow/alarms.ts, linking to/releases. It is not limited to admins, likeno_releaseandmain_ahead, because its text holds no private repository names. So home,/boards, the notifier and the MCPflowtool all show it without a new tool. - Not done:
beatLineandbeatFactalready say "silent" on/releasesand the terminal, so they are unchanged. Themain_aheadalarm keeps mentioning the tick as one of its facts.
test/alarm-release-quiet.test.ts covers:
- 59 minutes quiet gives no alarm.
- 61 minutes gives exactly one
firedAlarmsentry with the exact wording and start time. - A new heartbeat clears it.
- No heartbeat and no release gives no alarm.
- No heartbeat after a release: no alarm 30 minutes after the cut, an alarm 61 minutes after it.
Each case fails without the new module.
Checks: bun scripts/gate.ts and bun x tsc --noEmit pass. scripts/fast.ts named 530 test files, over its 5-minute budget, so I ran 116 that cover this change instead: this test, mcp-size, keeper-heartbeat, every flow-alarm-* test, and the notify, flow and home suites. 755 passed, 0 failed, and 8 did not run (763 in total), so the full check on this push is the real verdict. During that run the sandbox blocked one outbound connection to sunny-day--localhost:80; it did not cause a failure.
To try it: post a heartbeat to POST /api/v1/release/heartbeat, wait more than an hour, and the alarm shows on home and in flow.
🤖 Generated with Claude Code
Two changes, both small. (1) Disclosure: the alarm's text embeds beat.reason and is not admin-only, so it reaches home, /boards, the notifier and the MCP flow tool. docs/features/release-heartbeat.md says the reason is shown only to readers of /releases (the main_ahead issue names outcome and time, never the reason) because it may be public. Either gate release_quiet like the other private alarms (publicOnly) or drop the reason from the words for readers who cannot see /releases, and add a test with publicOnly true. (2) Docs: the feature has no how-to. Add the release_quiet alarm (60 min, never-beat-after-a-release case, clears on a fresh beat) to docs/features/release-heartbeat.md next to main_ahead. Otherwise the module, wiring and tests look right; the preview answers 200. Noted: the import in alarms.ts is out of alphabetical order (release_quiet before red_main).
The release_quiet flow alarm fires once when the release tick has not sent a heartbeat for more than 60 minutes. It also fires on a forge that has cut a release but never had a beat, timed from the newest cut. The next beat clears it.
Changes from review:
- The alarm no longer shows the tick's reason. It now says how long ago the tick was last heard, the outcome (cut, skipped or failed), and "its reason is on /releases", and it links there. Before, the reason went to every reader, including home, /boards, the notifier and the MCP
flowtool.
- I dropped the reason for every reader rather than hiding the alarm behind publicOnly. A quiet release tick is about the forge itself, not about any private repository, so public readers should still be warned. Leaving out only the reason follows the rule the main_ahead issue already uses.
- New test with
publicOnly: true. The beat's reason is a made-up private repository name. The test checks that the alarm still fires, shows the outcome, and never contains the reason, for both public-only and full readers. - How-to added to
docs/features/release-heartbeat.md, next tomain_ahead. It covers the 60-minute limit (SILENT_MS), the case where a forge that has released has never had a beat, the forge that has never released staying quiet, clearing on any fresh beat, and why the reason is left out. - Import order: the
releaseQuietAlarmimport insrc/flow/alarms.tsnow sits afteralarm_repeat, so the alarm imports are alphabetical.
The type check, scripts/gate.ts and test/alarm-release-quiet.test.ts (6 tests) all pass. To try it, record a beat older than an hour (POST /api/v1/release/heartbeat with an admin token, or recordBeat in a test). The alarm then appears on home and /boards without the reason.
🤖 Generated with Claude Code
Earlier asks met: the alarm no longer carries the tick reason (it names outcome and points to /releases), a publicOnly test proves it, the how-to is in docs/features/release-heartbeat.md (not README), and imports are ordered. The module is small, pure of the clock, with one bounded query only when no beat exists. Two next things: Non-blocking: wrap the long comment line in alarm_release_quiet.ts at the main_ahead mention. Non-blocking: consider a one-line mention on the /flow terminal screen test that release_quiet appears there.
When the release tick has not reported a heartbeat for more than 60 minutes, the forge now raises one
release_quietflow alarm. Its text says how long ago the tick last reported, what it did then, and the reason it gave. For example: "The release tick was last heard from 1 h 1 min ago (skipped): main has not moved". A new heartbeat clears it.src/flow/alarm_release_quiet.ts, built the same way asalarm_silent.ts. It readslastBeat(db)and takesnowas an argument. The 60 minutes is theSILENT_MSconstant thatsrc/keeper/beat.tsalready exports, and the time is worded with itsagoWordshelper. Nothing inbeat.tschanged.keeper_releasetable has a row. In that case a forge with no heartbeat ever gets the alarm once its newest release was cut more than 60 minutes ago, and the alarm's start time is that cut. A forge that has never released says nothing. This check is oneLIMIT 1query, and it runs only when there is no heartbeat at all.release_quietis added tofiredAlarmsinsrc/flow/alarms.ts, linking to/releases. It is not limited to admins, likeno_releaseandmain_ahead, because its text holds no private repository names. So home,/boards, the notifier and the MCPflowtool all show it without a new tool.beatLineandbeatFactalready say "silent" on/releasesand the terminal, so they are unchanged. Themain_aheadalarm keeps mentioning the tick as one of its facts.test/alarm-release-quiet.test.tscovers:firedAlarmsentry with the exact wording and start time.Each case fails without the new module.
Checks:
bun scripts/gate.tsandbun x tsc --noEmitpass.scripts/fast.tsnamed 530 test files, over its 5-minute budget, so I ran 116 that cover this change instead: this test,mcp-size,keeper-heartbeat, everyflow-alarm-*test, and the notify, flow and home suites. 755 passed, 0 failed, and 8 did not run (763 in total), so the full check on this push is the real verdict. During that run the sandbox blocked one outbound connection tosunny-day--localhost:80; it did not cause a failure.To try it: post a heartbeat to
POST /api/v1/release/heartbeat, wait more than an hour, and the alarm shows on home and inflow.🤖 Generated with Claude Code
Closes #11890