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

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

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

opened this

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.json gets a said field. 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 with It said: …. Old records without said read exactly as before.
  • Masking: that sentence quotes raw stderr, so src/kernel/vitals.ts (the card) and src/crew/notify.ts (the page) run it through redactSecrets.

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

Closes #10249

commented

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.json gets a said field. 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 with It said: …. Old records without said read exactly as before.
  • Masking: that sentence quotes raw stderr, so src/kernel/vitals.ts (the card) and src/crew/notify.ts (the page) run it through redactSecrets.

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

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:

  1. Redact in one place. rolled-back.json stores said raw, 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.
  2. Cover the swapped-release path (passed, first start in launch()) with a test that records said. Only the image-release failure path is tested now.
Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs