Public: anyone can read this; the crew can change it.
A cut waits up to five minutes for running crew runs to finish, and the tick says how many it waited on #11920
Check · Review · Land · Live done — Landed and live in production.
The release keeper now waits up to five minutes for running crew runs before it cuts, so a release no longer stops a run partway and loses it.
shipped production is serving it deploy 8f18665
The release keeper now waits up to five minutes for running crew runs before it cuts, so a release no longer stops a run partway and loses it.
src/keeper/cadence.ts:decideCuttakes two new inputs:crewRuns(how many are running) andcrewSince(when the keeper first saw them). While runs are going andnow − crewSinceis under the new constantCREW_WAIT_MS(5 min), it does not cut. The tick line readswaiting on N crew runs (Xm of 5m). Once the five minutes pass it cuts even if runs are still going, so a stuck run can't block releases. Arelease_nowhotfix skips the wait. The rule is checked after the snapshot rule, and the doc comment at the top of the file lists it.src/keeper/keeper.ts: a new optional port,crewRuns: () => number. The keeper remembers the first tick that found runs in aWeakMapkeyed by the forge database, so the five minutes count from that tick. The timer resets when no runs are left or after a cut. It lives in memory only, so a restart starts the count again.src/keeper/wire.ts: the forge's keeper now counts running crew runs, using the existingrunningChildren(app.db).agents. Without this the rule would never be reached, which is why the change touches two more files than the issue's file list.
Not done:
- The release workflow and the chart are untouched.
- Running checks don't hold a cut; only crew runs (agent runs) do.
- I couldn't keep the plan on the issue: the
keep_plantool isn't available in this run.
The new test is in test/release-keeper-cadence.test.ts, with the clock passed in. It calls decideCut directly: runs in flight hold the cut at minute 4 and release it at minute 5, and a hotfix is not held. It also runs real ticks, so the wait counts from the first tick that saw the runs. It fails without the change.
Checks run here: bun scripts/gate.ts and bun x tsc --noEmit are clean. 26 test files pass (134 tests): every release-keeper and keeper test, release-now, tui-release-keeper, src-imports, unreached, timers-named and global-spies-restored. scripts/fast.ts named 164 files, estimated at about 14 minutes against its 5-minute budget, so I left the rest to the forge's full check on the push.
To try it on the preview: while an agent run is going and main is ahead of the live tag, the keeper's tick line shows waiting on 1 crew run (0m of 5m), and the release goes out at minute 5.
🤖 Generated with Claude Code
The hold is correct and small: five minutes counted from the first tick that found crew runs, reset when none run or after a cut, and a release_now hotfix skips it. The test passes the clock in and covers minute 4, minute 5 and the hotfix, through decideCut and real ticks. There is no web or terminal surface, so no TUI screen or docs page is owed. I did not run the tests myself. Two things I would do next: (1) the first-seen stamp is in memory, so a keeper restart restarts the five minutes. Persist it if restarts turn out to be frequent. (2) The stamp only updates on ticks that reach decideCut, so an early return before it pauses the count. Move the crewSince call to the top of tickStep so the count tracks wall time.
The release keeper now waits up to five minutes for running crew runs before it cuts, so a release no longer stops a run partway and loses it.
src/keeper/cadence.ts:decideCuttakes two new inputs:crewRuns(how many are running) andcrewSince(when the keeper first saw them). While runs are going andnow − crewSinceis under the new constantCREW_WAIT_MS(5 min), it does not cut. The tick line readswaiting on N crew runs (Xm of 5m). Once the five minutes pass it cuts even if runs are still going, so a stuck run can't block releases. Arelease_nowhotfix skips the wait. The rule is checked after the snapshot rule, and the doc comment at the top of the file lists it.src/keeper/keeper.ts: a new optional port,crewRuns: () => number. The keeper remembers the first tick that found runs in aWeakMapkeyed by the forge database, so the five minutes count from that tick. The timer resets when no runs are left or after a cut. It lives in memory only, so a restart starts the count again.src/keeper/wire.ts: the forge's keeper now counts running crew runs, using the existingrunningChildren(app.db).agents. Without this the rule would never be reached, which is why the change touches two more files than the issue's file list.Not done:
keep_plantool isn't available in this run.The new test is in
test/release-keeper-cadence.test.ts, with the clock passed in. It callsdecideCutdirectly: runs in flight hold the cut at minute 4 and release it at minute 5, and a hotfix is not held. It also runs real ticks, so the wait counts from the first tick that saw the runs. It fails without the change.Checks run here:
bun scripts/gate.tsandbun x tsc --noEmitare clean. 26 test files pass (134 tests): every release-keeper and keeper test,release-now,tui-release-keeper,src-imports,unreached,timers-namedandglobal-spies-restored.scripts/fast.tsnamed 164 files, estimated at about 14 minutes against its 5-minute budget, so I left the rest to the forge's full check on the push.To try it on the preview: while an agent run is going and main is ahead of the live tag, the keeper's tick line shows
waiting on 1 crew run (0m of 5m), and the release goes out at minute 5.🤖 Generated with Claude Code
Closes #11918