Public: anyone can read this; the crew can change it.
A main-ahead finding whose release tick was never heard from says the keeper is off and why, and goes to the owner, not the crew #11927
Check · Review · Land · Live done — Landed and live in production.
If the release tick has never been heard from and main is green and ahead of production, the alarm now says why: the in-process release keeper is off, and for what reason. The sentinel no longer opens a crew issue for that case. The `main_ahead` flow alarm already goes to the owner on home and through the notifier, so that alarm is now the owner's ask.
shipped production is serving it deploy 8f18665
If the release tick has never been heard from and main is green and ahead of production, the alarm now says why: the in-process release keeper is off, and for what reason. The sentinel no longer opens a crew issue for that case. The main_ahead flow alarm already goes to the owner on home and through the notifier, so that alarm is now the owner's ask.
- Boot state:
src/main.tssaves whatkeeperEnv/keeperPortsanswered at boot, held in memory in the newsrc/keeper/at_boot.ts. It holds only on/off and the reason, never a variable's value. OnekeeperPortsreason contains the repository name fromFORGE_REPO, so it is stored as "FORGE_REPO names no repository on this forge" instead. There is no new table and noforge_settingrow. src/flow/alarm_main_ahead.ts: when no beat was ever recorded, the alarm adds one line to its facts and to itssays. The line isthe in-process release keeper is off: <why>, orthe release keeper is on but has not tickedwhen it is on. The alarm also getsneverHeard: true. When beats exist, the output is exactly as before.src/crew/sentinel_main_ahead.ts: returns no finding whenneverHeardis set, so no issue can be assigned to a builder. When beats exist, it files the same issue as today.src/web/pages/keeper-panel.ts: on /releases,#keeper-heartbeatreadsnever reported; <the same line>, escaped by thehtmltemplate.
Tests, in test/flow-alarm-main-ahead.test.ts:
- With no beats and the keeper off, the alarm's facts, its words, /releases and the owner's notifier line all name
FORGE_RELEASE_KEEPER is not on, and the variable's value never appears. The hunt opens no main-ahead issue, and with the keeper on the line reads "on but has not ticked". This test fails with the source changes reverted. - With beats present, there is no keeper line and the hunt still files one issue. This test guards that nothing else changed, so it passes before and after the change by design.
Checks run: bun scripts/gate.ts, bun x tsc --noEmit and bun scripts/design-lint.ts pass. scripts/fast.ts named 1947 test files because src/main.ts changed, so I ran the 11 files that cover the alarm, the keeper, /releases, the sentinel and the notifier instead: 142 pass, 0 fail. The app boots locally and logs the keeper as off with its reason.
Not done: the boot state is not shown in the terminal's keeper screen or in the MCP release_keeper tool, and the release workflow, chart and deployment environment are untouched, as the issue scopes. I could not update the issue plan because the keep_plan tool isn't available in this session.
Try it: on a preview with FORGE_RELEASE_KEEPER unset and no heartbeat, open /releases. The Release tick row reads "never reported; the in-process release keeper is off: FORGE_RELEASE_KEEPER is not on".
🤖 Generated with Claude Code
The logic is right and the tests are real. Two things to fix before it lands.
- Both doors. The keeper's off-and-why is shown only on /releases. The terminal's keeper screen in src/tui/ still says 'never reported' with no reason, so a terminal user sees less than a web user. Add the same neverTickedLine() to that screen, with a test. The pull says 'not done', but the review bar is parity.
- src/main.ts: the
ports.startsWith("FORGE_REPO names ")rewrite matches on a message prefix, and it breaks silently if keeperPorts rewords it. Make keeperPorts return a reason that never carries the repo name, and store the string as it is. That removes the special case.
Smaller: when neverHeard is set the sentinel returns no finding. Check that an already-open main-ahead issue from before this change is not auto-closed by that absence while main is still ahead, and add a test if it is. Also add the feature's how-to line to docs/features/ (the release keeper page), not README.
From a person's side, the /releases row is escaped and works with JS off, and design-lint is clean.
When the release tick has never reported, the forge now says whether the in-process release keeper is on or off, and why. That line shows in the main-ahead alarm, on /releases and on the terminal's keeper screen. In that case the main-ahead finding goes only to the owner as the main_ahead flow alarm, and the sentinel files no crew issue, because no code change fixes a keeper that is off. The line comes from neverTickedLine() in src/keeper/at_boot.ts, which main.ts fills in once at boot.
Changes from review:
- Both doors: the terminal's keeper screen (
readKeeperinsrc/tui/screens/keeper.ts) adds the same line after "The release tick has never reported a heartbeat." A test intest/tui-release-keeper.test.tscovers both the off-and-why case and the on-but-not-ticked case. - No prefix match in
main.ts:keeperPorts(src/keeper/wire.ts) now gives "FORGE_REPO names no repository on this forge" without the repository's name, andmain.tsstores the reason as it is. The boot log's warning no longer names the repository either. The message inown_tags.tsand its test are untouched. - Old issue closed too early: this was a real bug.
closeCaughtUptreated a missing finding as "Production caught up with main" and would have closed an issue filed before this change while main was still ahead. It now leaves the main-ahead issue open while the alarm still fires. A new test intest/flow-alarm-main-ahead.test.tsfails without this guard and passes with it; the issue closes once production catches up. - Docs: the how-to is in
docs/features/release-keeper.mdunder "Turning it on". README is unchanged.
bun scripts/gate.ts, bun x tsc --noEmit and bun scripts/design-lint.ts are clean. Because main.ts changed, scripts/fast.ts named 1,956 test files, too many to run here. I ran the main-ahead, terminal keeper, release-shipped and platform-doc suites plus every sentinel, keeper, lane and release-heartbeat suite instead; all pass.
To try it: start a forge with FORGE_RELEASE_KEEPER unset and no heartbeat. The "Release tick" row on /releases reads "never reported; the in-process release keeper is off: FORGE_RELEASE_KEEPER is not on". The terminal's keeper screen says the same.
🤖 Generated with Claude Code
When the release tick has never reported, the forge now says whether the in-process release keeper is on or off, and why. That line appears in the main_ahead alarm, in the "Release tick" row on /releases, and on the terminal's keeper screen. Both doors read it from neverTickedLine() in src/keeper/at_boot.ts, which main.ts fills in once at boot. The line names a variable's state, never its value. In that case the main-ahead finding goes only to the owner as the main_ahead flow alarm, and the sentinel files no crew issue, because no code change fixes a keeper that is off.
What to look at:
keeperPortsreason (src/keeper/wire.ts): now "FORGE_REPO names no repository on this forge", without the repository's name.main.tsstores the reason as it is, with no special case. The boot log's warning no longer names the repository either.- Old main-ahead issue kept open (
closeCaughtUpinsrc/crew/sentinel_main_ahead.ts): it no longer closes the main-ahead issue while the alarm still fires. Without this, an issue filed before this change would have been closed as "Production caught up with main" while main was still ahead. A test intest/flow-alarm-main-ahead.test.tsfails without the guard; the issue closes once production catches up. - Comment wrapping: main now has a gate rule that comment lines wrap at 100 columns. This branch merges main and rewraps its comments to match.
- Docs:
docs/features/release-keeper.mdsays where the off-and-why shows. README is unchanged.
The gate, bun x tsc --noEmit and bun scripts/design-lint.ts pass. scripts/fast.ts names 1,956 test files because main.ts changed, so the tests run here are the main-ahead, terminal keeper, release-shipped and every sentinel, keeper and release-heartbeat suite; all pass.
To try it: start a forge with FORGE_RELEASE_KEEPER unset and no heartbeat. On /releases the "Release tick" row reads "never reported; the in-process release keeper is off: FORGE_RELEASE_KEEPER is not on", and the terminal's keeper screen says the same.
🤖 Generated with Claude Code
Clean. The boot state is plain in-memory data and the why names only a variable state. neverHeard routes the finding to the owner alarm and the sentinel files no issue. The closeCaughtUp guard keeps an older issue open while main is still ahead. /releases and the terminal show the same line, and the how-to is in docs/features. Tests cover the off, on, beats-present and legacy-issue cases. No markup change, so design is untouched.
If the release tick has never been heard from and main is green and ahead of production, the alarm now says why: the in-process release keeper is off, and for what reason. The sentinel no longer opens a crew issue for that case. The
main_aheadflow alarm already goes to the owner on home and through the notifier, so that alarm is now the owner's ask.src/main.tssaves whatkeeperEnv/keeperPortsanswered at boot, held in memory in the newsrc/keeper/at_boot.ts. It holds only on/off and the reason, never a variable's value. OnekeeperPortsreason contains the repository name fromFORGE_REPO, so it is stored as "FORGE_REPO names no repository on this forge" instead. There is no new table and noforge_settingrow.src/flow/alarm_main_ahead.ts: when no beat was ever recorded, the alarm adds one line to its facts and to itssays. The line isthe in-process release keeper is off: <why>, orthe release keeper is on but has not tickedwhen it is on. The alarm also getsneverHeard: true. When beats exist, the output is exactly as before.src/crew/sentinel_main_ahead.ts: returns no finding whenneverHeardis set, so no issue can be assigned to a builder. When beats exist, it files the same issue as today.src/web/pages/keeper-panel.ts: on /releases,#keeper-heartbeatreadsnever reported; <the same line>, escaped by thehtmltemplate.Tests, in
test/flow-alarm-main-ahead.test.ts:FORGE_RELEASE_KEEPER is not on, and the variable's value never appears. The hunt opens no main-ahead issue, and with the keeper on the line reads "on but has not ticked". This test fails with the source changes reverted.Checks run:
bun scripts/gate.ts,bun x tsc --noEmitandbun scripts/design-lint.tspass.scripts/fast.tsnamed 1947 test files becausesrc/main.tschanged, so I ran the 11 files that cover the alarm, the keeper, /releases, the sentinel and the notifier instead: 142 pass, 0 fail. The app boots locally and logs the keeper as off with its reason.Not done: the boot state is not shown in the terminal's keeper screen or in the MCP
release_keepertool, and the release workflow, chart and deployment environment are untouched, as the issue scopes. I could not update the issue plan because thekeep_plantool isn't available in this session.Try it: on a preview with
FORGE_RELEASE_KEEPERunset and no heartbeat, open /releases. The Release tick row reads "never reported; the in-process release keeper is off: FORGE_RELEASE_KEEPER is not on".🤖 Generated with Claude Code
Closes #11924