> > From emacs without any special config (/bin/sh -i showing as the process > name): sending SIGUSR2 from emacs *actually works*! I'm not sure how it is > passing a keyless signal over the SSH connection, but I have confirmed that > it is.
OK, progress. I now understand that TRAMP intercepts calls to signal-process in `tramp-signal-process', and in that function, it simply sends, e.g., `kill -USR2 PID` to the remote PID, via SSH. Similarly with `interrupt-process'. So far, so good. However, when `direct-async' is configured for a process, TRAMP for some reason does not have access to the remote PID: its 'remote-pid PLIST entry is missing. This means it cannot send signals or interrupts in that manner. If you attempt to signal or interrupt a direct-async process, TRAMP will silently pass that along to Emacs internal code. Emacs then delivers the signal to the client SSH process itself, killing it. One way around all this I've found: setup `direct-async' as we discussed (full, no "-t -t"), then, upon startup, collect the PID from the process itself (e.g., in python: import os; os.getpid()). Set this PID into the process' plist as 'remote-pid. Good news: this actually works well for signals! Unfortunately `tramp-interrupt-process' causes problems here, due to this unusual bit at the end: ;; Wait, until the process has disappeared. If it doesn't, ;; fall back to the default implementation. (while (tramp-accept-process-output proc)) (not (process-live-p proc))))) I must admit I don't understand this code. If the process remains alive after the interruption is sent, the "default implementation" is subsequently employed, which, as mentioned above, kills the SSH process itself. Long-lived processes should _not_ necessarily exit upon interrupt. Many of them, e.g. the Python REPL, specifically catch SIGINT and use that to interrupt their own running commands. I wonder if this behavior can be gated behind a custom variable, like `tramp-interrupt-ends-process'. So something like: ;; If we expect interrupt to end the process, wait until it ;; has disappeared. If it doesn't, fall back to the default ;; implementation. (if tramp-interrupt-ends-process (progn (while (tramp-accept-process-output proc)) (not (process-live-p proc))) t))))) ; handled