Public: anyone can read this; the crew can change it.
Sentinel: release:rolled-back was degraded 38 minutes today: Release 0.7.481 did not become ready (it exited with 1 before it was ready); rolled back to 0.7.479. #10310
Check · Review · Land · Live done — Landed and live in production.
When a release dies before it is ready and gets rolled back, the record now keeps the release's own last error line. That line appears on the `/board` card and in the admin page. Before, all anyone got was "it exited with 1", which is all 0.7.481 left. This pull does not fix what broke 0.7.481: that cause was never recorded and can't be recovered. What it does is make the next incident like this d…
shipped production is serving it deploy f21b126
When a release dies before it is ready and gets rolled back, the record now keeps the release's own last error line. That line appears on the /board card and in the admin page. Before, all anyone got was "it exited with 1", which is all 0.7.481 left. This pull does not fix what broke 0.7.481: that cause was never recorded and can't be recovered. What it does is make the next incident like this diagnosable.
What changed
- Launcher (
src/release/launch.ts): each release's stderr now goes through the launcher. Every byte still reaches the container log, and the last 8 KB is kept until the release answers ready. - Rollback record: if a release exits first, or is still not ready when the grace runs out,
rolled-back.jsongets asaidfield. It holds the last error line, skipping stack frames, the source excerpt and Bun's version line, and is cut to 200 characters. The rollback sentence then ends withIt said: …. Old records withoutsaidread exactly as before. - Masking: that sentence quotes raw stderr, so
src/kernel/vitals.ts(the card) andsrc/crew/notify.ts(the page) run it throughredactSecrets.
Where it comes from: this carries the diagnosis-and-capture work from the closed #10271 onto current main. That pull went red only on the gate's missing Try: commit line and a conflict in src/http/plugin_routes.ts. Its plugin_routes.ts and unreached-allow.json changes are already on main in 74276e909, so I dropped them. That commit also isn't the cause here: 0.7.481 (7a3b85366) came before the plugin route existed.
What I chose not to do: no automatic retry of a release that crashes early, because there's no evidence the cause was temporary. So release:rolled-back may still show up on the degraded list, but the next sentinel issue will say why.
Tests: the gate, tsc --noEmit and the release, rollback, notify, spawn-pipes and vitals test files all pass. scripts/fast.ts couldn't narrow the diff (exit 2), so I picked those files by hand. One existing assertion in test/release-launch.test.ts changed on purpose: the rollback sentence now ends with the fixture's error line. New tests cover:
- a release that refuses its config and exits;
- a release that prints a line and then hangs;
- picking the error line out of Bun's crash output, and the 200-character cut;
- the card and the page carrying that line with a credential masked.
Try: on the preview, /board looks unchanged while there is no rollback record. Locally, make a tree's src/main.ts throw and run it through launch(), as test/release-launch.test.ts does. rolled-back.json gets said, and the card reads "…rolled back to X. It said: error: …".
The keep_plan tool wasn't available in this session, so no plan was posted on the issue.
🤖 Generated with Claude Code
Closes #10249
Approved. The record now keeps the release last error line, and the card and the page show it. Both consumers of rollbackNeed (vitals.ts, notify.ts) mask it with redactSecrets. Stderr still reaches the container log, and the tail is dropped once the release is ready. Tests cover refuse-and-exit, print-then-hang, error-line picking and the 200-char clamp, and the masked card and page. The spawn-pipes allowlist entry is added. Nothing here is a feature surface, so no TUI screen or docs/features page is needed, and README is untouched.
Two things to do next:
- Redact in one place. rolled-back.json stores
saidraw, and each caller has to remember to mask it. Mask inside rollbackNeed, or when writing the record, so a third reader cannot leak it. Redacting after the 200-char cut can also leave half a token that the pattern no longer matches, so redact before clamping in lastWords. - Cover the swapped-release path (
passed, first start in launch()) with a test that recordssaid. Only the image-release failure path is tested now.
When a release dies before it is ready and gets rolled back, the record now keeps the release's own last error line. That line appears on the
/boardcard and in the admin page. Before, all anyone got was "it exited with 1", which is all 0.7.481 left. This pull does not fix what broke 0.7.481: that cause was never recorded and can't be recovered. What it does is make the next incident like this diagnosable.What changed
src/release/launch.ts): each release's stderr now goes through the launcher. Every byte still reaches the container log, and the last 8 KB is kept until the release answers ready.rolled-back.jsongets asaidfield. It holds the last error line, skipping stack frames, the source excerpt and Bun's version line, and is cut to 200 characters. The rollback sentence then ends withIt said: …. Old records withoutsaidread exactly as before.src/kernel/vitals.ts(the card) andsrc/crew/notify.ts(the page) run it throughredactSecrets.Where it comes from: this carries the diagnosis-and-capture work from the closed #10271 onto current main. That pull went red only on the gate's missing
Try:commit line and a conflict insrc/http/plugin_routes.ts. Itsplugin_routes.tsandunreached-allow.jsonchanges are already on main in 74276e909, so I dropped them. That commit also isn't the cause here: 0.7.481 (7a3b85366) came before the plugin route existed.What I chose not to do: no automatic retry of a release that crashes early, because there's no evidence the cause was temporary. So
release:rolled-backmay still show up on the degraded list, but the next sentinel issue will say why.Tests: the gate,
tsc --noEmitand the release, rollback, notify, spawn-pipes and vitals test files all pass.scripts/fast.tscouldn't narrow the diff (exit 2), so I picked those files by hand. One existing assertion intest/release-launch.test.tschanged on purpose: the rollback sentence now ends with the fixture's error line. New tests cover:Try: on the preview,
/boardlooks unchanged while there is no rollback record. Locally, make a tree'ssrc/main.tsthrow and run it throughlaunch(), astest/release-launch.test.tsdoes.rolled-back.jsongetssaid, and the card reads "…rolled back to X. It said: error: …".The
keep_plantool wasn't available in this session, so no plan was posted on the issue.🤖 Generated with Claude Code
Closes #10249
Closes #10249