Commit f47e41a
fix: scope-intercept signal handling for long-lived workloads (#309)
## Why
`scope-intercept` is sometimes used as a shebang on long-running scripts
to enable self-healing known-error fix-and-retry. We had a report that
Ctrl+C stopped working in that configuration: services started under
overmind (nginx, etc.) kept running, the overmind socket was orphaned,
and the next start failed with an "overmind previously crashed" prompt.
The terminal sends SIGINT to every member of the foreground process
group — both `scope-intercept` and the wrapped `env -S bash`. With no
signal handler, `scope-intercept` followed the default and aborted
immediately. Its captured stdout/stderr pipes closed, and the child died
with SIGPIPE before its own SIGINT trap (and overmind's shutdown logic)
could complete.
`scope-intercept` was originally designed for short-lived setup scripts,
but using it as a shebang for long-running interactive processes is a
strong fit *if* it cooperates with the child's lifecycle. This PR makes
it cooperate.
## What changed
**`src/bin/scope-intercept.rs`** — At the top of `run_command`, install
Tokio Unix signal listeners for `SIGINT`, `SIGTERM`, and `SIGHUP`. Their
only job is to consume the default-terminate behavior so
`scope-intercept` stays alive while the child runs its cleanup. No
manual forwarding is needed — the OS already delivered the signal to the
child via the shared process group.
When the interrupt flag is set and the child exited non-zero, skip the
known-error analysis, retry, and bug-report flows. The user asked to
quit, not to be quizzed.
`OutputCapture::capture_output` and the `scope doctor` path are
deliberately not touched — this fix is scoped to the intercept binary.
**`Cargo.toml`** — Adds `libc` as a dev-dep so the new test can call
`setsid`/`killpg`.
## Tests
**`tests/scope_intercept.rs::test_intercept_forwards_sigint_and_waits_for_child_cleanup`**:
- Writes a `trap.sh` that traps SIGINT, writes `cleanup.done`, and
sleeps.
- Spawns `scope-intercept -- bash trap.sh` in its own session/process
group via `pre_exec(setsid)`.
- Waits for a `ready` marker, then sends SIGINT to the *group* via
`killpg` — this is what a terminal does on Ctrl+C.
- Asserts: the child's trap ran (`cleanup.done` exists with the expected
contents), `scope-intercept` exited within 10s, and the exit code is
130.
Verified the test fails on `main` (without the fix, exit status is
`unix_wait_status(2)` — `scope-intercept` killed mid-flight by SIGINT)
and passes with the fix.
All 8 existing intercept tests still pass; full suite (177 tests) green.
## Test plan
- [x] `cargo test --test scope_intercept` — 8/8 pass
- [x] `cargo test` — 177/177 pass
- [x] `cargo fmt --check` clean
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>1 parent d8a78fc commit f47e41a
4 files changed
Lines changed: 145 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
80 | 80 | | |
81 | 81 | | |
82 | 82 | | |
| 83 | + | |
83 | 84 | | |
84 | 85 | | |
| 86 | + | |
85 | 87 | | |
86 | 88 | | |
87 | 89 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
| 9 | + | |
9 | 10 | | |
| 11 | + | |
10 | 12 | | |
11 | 13 | | |
12 | 14 | | |
| |||
76 | 78 | | |
77 | 79 | | |
78 | 80 | | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
79 | 101 | | |
80 | 102 | | |
81 | 103 | | |
| |||
93 | 115 | | |
94 | 116 | | |
95 | 117 | | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
96 | 125 | | |
97 | 126 | | |
98 | 127 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
| 2 | + | |
| 3 | + | |
2 | 4 | | |
3 | 5 | | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
4 | 10 | | |
5 | 11 | | |
6 | 12 | | |
| |||
158 | 164 | | |
159 | 165 | | |
160 | 166 | | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
0 commit comments