From dbb0138dd19852cc8c50aa91f1d91ee311790402 Mon Sep 17 00:00:00 2001 From: Jacob Stephens Date: Fri, 25 Sep 2026 13:43:45 +0000 Subject: [PATCH 1/2] Treat SIGHUP like SIGINT and SIGTERM A closed terminal killed thirdshift on the spot, leaving the agent session running, the plugin temp dir behind, and no failure commit. SIGHUP now sets the interrupt flag, so the Run goes through the Failed run path and cleans up. Co-Authored-By: Claude Opus 5.5 --- README.md | 2 +- src/interrupt.rs | 8 ++++---- tests/failed_run.rs | 5 +++++ 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 24e8deb7b..74b588341 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,7 @@ A Run is not idempotent: re-running builds on whatever is already on the branch, ## Failed runs -A **Failed run** is one that ends, including by Ctrl-C, without an open pull request from its Issue branch that targets the Base branch, is mergeable and has passing CI. Causes include the session exiting non-zero, no pull request or one with the wrong base, and running out of Repairs. +A **Failed run** is one that ends, including by Ctrl-C or a closed terminal, without an open pull request from its Issue branch that targets the Base branch, is mergeable and has passing CI. Causes include the session exiting non-zero, no pull request or one with the wrong base, and running out of Repairs. A Failed run: diff --git a/src/interrupt.rs b/src/interrupt.rs index 0df1e6451..2f784d495 100644 --- a/src/interrupt.rs +++ b/src/interrupt.rs @@ -1,4 +1,4 @@ -//! SIGINT and SIGTERM: recorded rather than fatal, so an interrupted Run can +//! SIGINT, SIGTERM and SIGHUP: recorded rather than fatal, so an interrupted Run can //! still go through the Failed run path and clean up. use std::sync::Arc; @@ -6,14 +6,14 @@ use std::sync::OnceLock; use std::sync::atomic::{AtomicBool, Ordering}; use anyhow::{Context, Result}; -use signal_hook::consts::{SIGINT, SIGTERM}; +use signal_hook::consts::{SIGHUP, SIGINT, SIGTERM}; static REQUESTED: OnceLock> = OnceLock::new(); -/// Start recording SIGINT and SIGTERM instead of dying on them. +/// Start recording SIGINT, SIGTERM and SIGHUP instead of dying on them. pub fn install() -> Result<()> { let flag = REQUESTED.get_or_init(|| Arc::new(AtomicBool::new(false))); - for signal in [SIGINT, SIGTERM] { + for signal in [SIGINT, SIGTERM, SIGHUP] { signal_hook::flag::register(signal, Arc::clone(flag)) .context("could not install the signal handler")?; } diff --git a/tests/failed_run.rs b/tests/failed_run.rs index eb4174fa1..50c0f4779 100644 --- a/tests/failed_run.rs +++ b/tests/failed_run.rs @@ -166,6 +166,11 @@ fn sigterm_while_the_session_runs_stops_it_and_fails_the_run() { assert_interrupt_fails_the_run("TERM"); } +#[test] +fn sighup_while_the_session_runs_stops_it_and_fails_the_run() { + assert_interrupt_fails_the_run("HUP"); +} + fn assert_interrupt_fails_the_run(signal: &str) { let scenario = Scenario::new(); let started = scenario.path("agent-started"); From f32bc95cb8b0b01528dcd369f76a35b42231d783 Mon Sep 17 00:00:00 2001 From: Jacob Stephens Date: Fri, 25 Sep 2026 13:45:06 +0000 Subject: [PATCH 2/2] Rewrap the interrupt module doc Co-Authored-By: Claude Opus 5.5 --- src/interrupt.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/interrupt.rs b/src/interrupt.rs index 2f784d495..103abd8b0 100644 --- a/src/interrupt.rs +++ b/src/interrupt.rs @@ -1,5 +1,5 @@ -//! SIGINT, SIGTERM and SIGHUP: recorded rather than fatal, so an interrupted Run can -//! still go through the Failed run path and clean up. +//! SIGINT, SIGTERM and SIGHUP: recorded rather than fatal, so an interrupted +//! Run can still go through the Failed run path and clean up. use std::sync::Arc; use std::sync::OnceLock;