Skip to content

Interrupting a Spec run - #154

Merged
JacobStephens2 merged 3 commits into
mainfrom
issue-145
Sep 30, 2026
Merged

JacobStephens2 merged 3 commits into
mainfrom
issue-145

Conversation

@JacobStephens2

Copy link
Copy Markdown
Owner

Summary

Interrupting a Spec run is now safe. A signal the Spec run alone receives is passed on to the running Ticket's Run. That Run ends through its own Failed run path and pushes its work. The Spec run waits for it, then starts no further Ticket and opens no Spec PR. It ends as an interrupted Failed spec run with exit 1.

 run_ticket(#n)
   spawn child thirdshift (Merge run into the Spec branch)
-  relay stderr until EOF, then wait
+  relay stderr on its own thread
+  loop until the child exits
+    if interrupted and not yet passed on: SIGTERM the child
+  if it failed and the Spec run was interrupted: "#n interrupted", fail "interrupted"
 land_tickets: checks the interrupt before each Ticket (unchanged)
+open_spec_pr: fails "interrupted" before opening or readying the Spec PR

failed_run now has one interrupted_or remap. Both the Failed run path and From<anyhow::Error> use it, so an interrupt that surfaces as some other error, such as a killed gh, reads interrupted in the Spec run too.

Evidence

  • Before: the new tests/spec_run.rs tests failed. The Spec run's SIGTERM never reached Numbered Issue branches after a merged or closed PR #21's Run, so the agent slept for the full 30s ("the Ticket's session was not stopped").
  • After: all 14 spec_run tests pass, and so does the full suite (cargo test). cargo clippy --all-targets shows no warnings.
for signal in TERM, HUP, INT sent to the Spec run's pid alone, while #21's agent is mid-work:
  exit 1, stdout empty, last line "thirdshift: interrupted"
  origin issue-21 head is "thirdshift: failed run (interrupted)" with wip.txt
  "#21: interrupted" relayed, then "#21 interrupted"
  no session for #22, no "#22" line, no Spec PR

Merge Danger

Door: two-way

Blast Radius: small

Only a Spec run's interrupt handling changes. The one shared change is From<anyhow::Error> for FailedRun. Once a Run has been interrupted, an early failure in it now reads interrupted instead of whatever error the interrupt caused.

Unaddressed findings

Standards

  • The "interrupted" literal appears in several places. It was already spread across run.rs, poll.rs, session.rs and failed_run.rs, and pulling it into one constant is a refactor beyond this Ticket.
  • The test names differ between the INT test and the TERM/HUP tests. The INT test's name states the point it covers: a signal sent to the Spec run alone is passed on.

Spec

  • "SIGTERM … while two Tickets run ends both Ticket Runs": Tickets still run one at a time until Parallel Tickets: spec.parallel and parallel <n> #143 lands. When it does, the single-child forward needs to become a loop over every running child.
  • "the summary marking it interrupted": the only summary today is the final interrupted line. The per-Ticket summary is Going as far as it can: Failed spec runs #144's job.
  • "An interrupt during the Spec review … Spec PR not marked ready": there is no Spec review yet (Spec review before the Spec PR is ready #142). For now the Spec PR phase checks for an interrupt before it opens or readies the Spec PR. A signal that lands during those gh calls can still be missed. Spec review before the Spec PR is ready #142 and The Spec PR's Repair loop and Self-merge #146 rewrite this phase, so it is left for them.
  • The Spec PR's Repair loop: it doesn't exist yet (The Spec PR's Repair loop and Self-merge #146).
  • No test sends the signal to the whole process group: the Ticket Run's handling of a group-wide signal is the Run's own interrupt path, which failed_run.rs and merge_run.rs already test. A second signal only sets the flag again.
  • Race between the interrupt check and spawning a Ticket's Run: in that tiny window, a child could get SIGTERM before it installs its handler. It would die having done nothing: no worktree yet, and no work to lose.
  • A genuine Ticket failure followed by an interrupt is labelled "#n interrupted": the label is cosmetic, and the Spec run fails as interrupted either way.

Closes #145

🤖 Generated with Claude Code

JacobStephens2 and others added 3 commits September 29, 2026 23:58
… as interrupted

A signal the Spec run alone receives is forwarded to the Ticket's child
thirdshift, which goes down its Failed run path and pushes its work. The
Spec run waits for it, starts no further Ticket, opens no Spec PR, and
fails as interrupted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JacobStephens2
JacobStephens2 merged commit 1514aa8 into main Sep 30, 2026
13 checks passed
@JacobStephens2
JacobStephens2 deleted the issue-145 branch September 30, 2026 00:14
@JacobStephens2 JacobStephens2 mentioned this pull request Sep 30, 2026
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.

Interrupting a Spec run

1 participant