Skip to content

story-150: An interrupted planning session is judged on what it committed, whatever order its prompts were answered in - #301

Merged
jerodw merged 3 commits into
mainfrom
story/story-150
Sep 15, 2026
Merged

jerodw merged 3 commits into
mainfrom
story/story-150

Conversation

@jerodw

@jerodw jerodw commented Sep 15, 2026

Copy link
Copy Markdown
Owner

test_an_interrupt_still_commits_what_was_written_and_exits_130 failed under load and passed on a quiet machine, and it was filed as a flaky test. It was not flaky. It was intermittently catching a real defect, and this story found it.

main's try/except KeyboardInterrupt covered the session subprocess and nothing else. An interrupt arriving after the session — while the script was committing, stamping the mandate, validating, publishing, or waiting at one of its own prompts — left main through an uncaught KeyboardInterrupt, and the process died by the signal rather than exiting.

A shell reports a process killed by SIGINT as 128 plus the signal, so a developer's terminal shows 130 either way. That is why it went unnoticed. Anything reading the return code rather than watching a terminal sees -SIGINT for one and 130 for the other — and under load, the interrupt lands in the post-session work often enough to redden the suite.

What changed

A second guard around the post-session work, and INTERRUPTED_EXIT_CODE named once so both guards say the same thing.

Nothing is undone, retried or completed by it. Whatever the steps above had already committed, stamped, pushed or published stands exactly as they left it, and the steps below the interrupt did not happen — which is what dying by the signal already meant. All that changes is that the script exits with the status instead of dying of the signal, so a caller that is not a shell is told what a shell was already told.

The interrupt now also says what it left behind, rather than leaving a developer to infer it from a bare status.

What is still outside both guards

Everything main does above the session: the base refresh, the id reservation, the worktree, the build-state link, and the script's own workflow-confirmation prompt. An interrupt there still dies by the signal, and the comment on the constant says so rather than leaving the boundary to be discovered. That is a deliberate stopping point — those steps have nothing committed to report on — and naming it is what keeps the next reader from assuming the guard is total.

A note on the brief

The brief this was planned from (#298) asserted that the behaviour under test was correct and put it out of scope, and rated its own confidence medium for exactly the reason that mattered: the failing run captured the assertion and not the pty transcript, so the mechanism was inferred rather than observed. The inference was wrong. What looked like a race between a signal and two pty writes was a gap in the script's own interrupt handling, and the story established that before repairing it.

Planned from the brief filed under 298.

🤖 Generated with Claude Code

https://claude.ai/code/session_01NJzpgnY9aJ7K2godyjrJJR

…committed, whatever order its prompts were answered in
…tted, whatever order its prompts were answered in

Implemented by the l5 harness story workflow.
@jerodw
jerodw merged commit 1c8db42 into main Sep 15, 2026
3 checks passed
@jerodw
jerodw deleted the story/story-150 branch September 15, 2026 20:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant