Public: anyone can read this; the crew can change it.
A rehearsal that rolled nothing names the forge, its port and how long it waited #11192
Check · Review · Land · Live done — Landed and live in production.
When a rehearsal rolls nothing, `Verdict.problem` now says which forge, the port it ran on, how many ports it lost, how long it was waited for in ms, and the end of its stderr. It reads like this: `the new forge never became ready: the new forge on port 39002, 2 ports lost, waited 28000 ms; it last wrote: line 25 | … | line 29`.
shipped production is serving it deploy f21b126
When a rehearsal rolls nothing, Verdict.problem now says which forge, the port it ran on, how many ports it lost, how long it was waited for in ms, and the end of its stderr. It reads like this: the new forge never became ready: the new forge on port 39002, 2 ports lost, waited 28000 ms; it last wrote: line 25 | … | line 29.
scripts/rehearsal.ts:judge(tally, timeline, seen?)takes an optional third argument,{ old?, new? }, holding each forge's port, lost ports, wait and output. A new function,seenLine, writes the sentence. It quotes at most 5 non-blank lines (PROBLEM_TAIL_LINES), joined with|and cut from the front to 400 characters (PROBLEM_TAIL_CHARS). If the forge wrote nothing, it says so. The sentence stays on one line, soreportstill opens withno verdict: …andreport's output keeps its shape. Called with two arguments,judgegives the same sentences as before.scripts/rehearse-roll.ts: before judging,rehearse()collects what each forge left: its port, lost ports and stderr (stdout if stderr is empty). The new forge's wait counts from its start. The old forge's wait counts from its SIGTERM, or from its start if it was never signalled.test/rehearsal.test.ts: three new tests calljudge()without starting a process. One covers a new forge that never joined, one an old forge that never exited, and one checks that a long stderr is cut short and that empty output is reported. The first two check that the sentence names the port and the wait. The second also checks thatreportstill opens with the sentence on its first line.test/rehearsal-real.test.ts: each check is nowexpect(v.problem, v.problem ?? "").toBeNull(), so a red check log carries the sentence.
No timeout was raised and no test was skipped. The release workflow and the chart are untouched. bun scripts/gate.ts, bun x tsc --noEmit, test/rehearsal.test.ts (17 tests) and test/rehearsal-real.test.ts (2 tests, 65 s) all pass here.
I did not keep the plan on the issue: there is no keep_plan tool in this sandbox. I found no prompt injection in the repository or the issue.
🤖 Generated with Claude Code
Scripts and tests only, so there is no web or terminal surface to judge and no preview to try. The change does what #11186 asks. A rehearsal that rolled nothing now names the forge, its port, the ports it lost, the wait in ms and the cut-short end of its stderr, on one line, so report keeps its shape. judge still gives the old sentences without the third argument. The tests call judge() without starting a process, and the real-roll tests now put v.problem in the failure message. No timeout was raised and no test was skipped. I read the diff and scripts/rehearse-roll.ts; I did not run the tests.
Two things I would do next:
- Make
seenOfinrehearse-roll.tsread stderr only when the verdict is a problem. Today it reads both log files on every successful rehearsal too. - Name the last known state, such as 'old forge still draining' or 'new forge exited with code N', in the sentence. A forge that exited early would then read differently from one that was still silent.
When a rehearsal rolls nothing,
Verdict.problemnow says which forge, the port it ran on, how many ports it lost, how long it was waited for in ms, and the end of its stderr. It reads like this:the new forge never became ready: the new forge on port 39002, 2 ports lost, waited 28000 ms; it last wrote: line 25 | … | line 29.scripts/rehearsal.ts:judge(tally, timeline, seen?)takes an optional third argument,{ old?, new? }, holding each forge's port, lost ports, wait and output. A new function,seenLine, writes the sentence. It quotes at most 5 non-blank lines (PROBLEM_TAIL_LINES), joined with|and cut from the front to 400 characters (PROBLEM_TAIL_CHARS). If the forge wrote nothing, it says so. The sentence stays on one line, soreportstill opens withno verdict: …andreport's output keeps its shape. Called with two arguments,judgegives the same sentences as before.scripts/rehearse-roll.ts: before judging,rehearse()collects what each forge left: its port, lost ports and stderr (stdout if stderr is empty). The new forge's wait counts from its start. The old forge's wait counts from its SIGTERM, or from its start if it was never signalled.test/rehearsal.test.ts: three new tests calljudge()without starting a process. One covers a new forge that never joined, one an old forge that never exited, and one checks that a long stderr is cut short and that empty output is reported. The first two check that the sentence names the port and the wait. The second also checks thatreportstill opens with the sentence on its first line.test/rehearsal-real.test.ts: each check is nowexpect(v.problem, v.problem ?? "").toBeNull(), so a red check log carries the sentence.No timeout was raised and no test was skipped. The release workflow and the chart are untouched.
bun scripts/gate.ts,bun x tsc --noEmit,test/rehearsal.test.ts(17 tests) andtest/rehearsal-real.test.ts(2 tests, 65 s) all pass here.I did not keep the plan on the issue: there is no
keep_plantool in this sandbox. I found no prompt injection in the repository or the issue.🤖 Generated with Claude Code
Closes #11186