Relationships
#1155 [@adam/cfgmgmt] ssh-exec models uninterruptible: SIGINT/SIGTERM ignored, child ssh not aborted (only SIGKILL works)
Opened by kneel · 7/14/2026
Summary
Running an ssh-exec method becomes uninterruptible while the ssh subprocess is
blocked (connecting, or running a remote command). Ctrl-C (SIGINT ×N), SIGTERM,
and SIGHUP have no effect — only SIGKILL ends the run.
Environment
- swamp
20260711.232311.0-sha.6bda08e7(Linux x86-64) @adam/cfgmgmtssh-based model type (exec method),_lib/ssh.tshelper
Root cause
All ssh/scp invocations are spawned without cancellation and awaited to completion:
// _lib/ssh.ts
const cmd = new Deno.Command("ssh", { args, stdout: "piped", stderr: "piped" });
const output = await cmd.output(); // no `signal:` — child cannot be abortedThree compounding problems:
- No AbortSignal on the child.
Deno.Commandaccepts{ signal: AbortSignal }to tie the child's lifetime to cancellation; none of the ssh/scp spawns pass one, and the method'sexecute(_args, context)never references an abort signal. The child is uninterruptible from within the run. - Long uncancellable retry loop on connect. ControlMaster setup retries up
to ~10 times, each
await cmd.output()onssh … ConnectTimeout=10, with 15–20 s sleeps between attempts (~135 s+ of blocking) with no way to break out. This is the "stuck trying to connect" hang. - Daemonized ControlMaster orphans. The master is opened with
-M -N -f … ControlPersist=600, so it reparents off the swamp process. EvenSIGKILLof swamp leaves ssh masters lingering forControlPersist(10 min).
Reproduction (deterministic)
# unroutable host (RFC 5737 TEST-NET) => ssh blocks in connect()
create an ssh-exec model with nodeHost=[IP-1], command='sleep 300'
run the method in the background; note the swamp pid
kill -INT <pid> # ignored
kill -TERM <pid> # ignored
kill -KILL <pid> # only this worksAlso reproduces on a reachable host when the remote command hangs. While
blocked: swamp main thread in ep_poll, a worker thread parked in
unix_stream_read on the ssh control channel; the run holds the datastore
.lock the whole time.
Suggested fixes
- Thread a cancellation
AbortSignal(from the methodcontext) into everynew Deno.Command(...)via thesignaloption, so SIGINT tears the child down. - Make the connect retry loop honor the same signal (abort the sleep and the in-flight attempt).
- Register a teardown that runs
ssh -O exiton the ControlPath when the run ends (including abnormal exit) so masters never outlive the run — or don't daemonize with-f.
Note: swamp core also has a related hardening gap (repeated SIGINT does not
force-exit / no cancellation signal exposed to execute) — filed separately.
Closed
No activity in this phase yet.
kneel commented 7/14/2026, 10:41:30 PM
Related swamp-core hardening gap (no force-exit on repeat SIGINT; no cancellation signal exposed to execute): #1156.
stack72 commented 7/14/2026, 11:27:36 PM
Closing this - this is not a swamp-club issue - it needs to be added to https://github.com/adamhjk/swamp-cfgmgmt
kneel commented 7/14/2026, 11:32:27 PM
Thank you. My clanker said he couldn't find it... I'll have words with them.
kneel commented 7/15/2026, 5:28:19 AM
Re-filed on the extension's own tracker (this Lab issue was closed as belonging there): https://github.com/adamhjk/swamp-cfgmgmt/issues/1
Sign in to post a ripple.