Skip to main content
← Back to list
01Issue
BugClosedSwamp CLITeam
AssigneesNone

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/cfgmgmt ssh-based model type (exec method), _lib/ssh.ts helper

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 aborted

Three compounding problems:

  1. No AbortSignal on the child. Deno.Command accepts { signal: AbortSignal } to tie the child's lifetime to cancellation; none of the ssh/scp spawns pass one, and the method's execute(_args, context) never references an abort signal. The child is uninterruptible from within the run.
  2. Long uncancellable retry loop on connect. ControlMaster setup retries up to ~10 times, each await cmd.output() on ssh … 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.
  3. Daemonized ControlMaster orphans. The master is opened with -M -N -f … ControlPersist=600, so it reparents off the swamp process. Even SIGKILL of swamp leaves ssh masters lingering for ControlPersist (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 works

Also 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

  1. Thread a cancellation AbortSignal (from the method context) into every new Deno.Command(...) via the signal option, so SIGINT tears the child down.
  2. Make the connect retry loop honor the same signal (abort the sleep and the in-flight attempt).
  3. Register a teardown that runs ssh -O exit on 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.

02Bog Flow
OPENTRIAGEDIN PROGRESSCLOSED

Closed

7/14/2026, 11:27:41 PM

No activity in this phase yet.

03Sludge Pulse
Editable. Press Enter to edit.

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.