I’ve got a command in an npm script that SSHes into a remote build server and runs a Bash script. The script sets up a lockfile and a trap call to delete the lockfile when the script exits.

I’m cancelling the operation with ctrl+C in both cases.

# cleanup function just deletes $LOCKFILEPATH
function mutex() {
  if [ -f "$LOCKFILEPATH" ]; then
    echo -e "\n\n${redtext}Build already in progress! Exiting.${resettext}\n\n";
    exit 1;
    touch $LOCKFILEPATH;
    trap cleanup EXIT;

This works fine when you first SSH into the host to run it, but the trap is not working when you send the command over SSH with

ssh hostname command

I tried adding to the trap command to run for more signals, but these don't seem to work either:


What should I be doing here?

I also set up a simpler script and it seemed to work fine when executing it manually over SSH. Maybe there's added layers when I’m running it using an npm script? The npm script is:

"deploy": "ssh HOSTNAME ''deploy-script $(git rev-parse --abbrev-ref HEAD) stage $npm_package_config_deploy_target yes''",

which just checks the current branch name and uses that to deploy on the build host. Same as

"deploy": "ssh HOSTNAME ''deploy-script CURRENTBRANCH stage APPNAME''",

UPDATE: Adding a force tty -t to the npm script seems to have fixed it. Confusing since I didn't need that for the simple script case. Maybe I'm spawning too many sub processes in the large script (too much to paste here without redacting a bunch) so it requires a tty to trigger the cleanup trap.

"deploy": "ssh -t HOSTNAME ''deploy-script CURRENTBRANCH stage APPNAME''",
  • 1
    You might need to add the full binary path for touch and trap. Common issue. Also, check out the Bash script I suggested be used in this answer that deals with PID lock logic to avoid a Bash script running over itself. Jan 25, 2018 at 0:23
  • If -t fixes the problem, doesn't that mean the trap only runs when you hit CTRL-C as a user? What if you SSH connection drops? Does the trap still work then? Nov 3, 2019 at 21:54

1 Answer 1


When you do

ssh hostname command

and then Ctrl+C, you terminate the local ssh. The command still runs on the remote side, it never gets your keystroke. This is different with -t. See this answer for explanation. The relevant fragment:

On the client side, ssh will try to set the tty used by stdin to "raw" mode, and sshd on the remote host will allocate a pseudo-tty […] if you type Ctrl+C, it will be sent to the remote host, where the command will likely receive SIGINT […]. The remote sshd then closes the connection, and ssh reports Connection to remotehost closed.

Therefore use:

ssh -t hostname command

and your command (not the local ssh) will get SIGINT when you press Ctrl+C.

This may get even more interesting. Compare ssh + here document – does Ctrl+C get to remote side?

  • This is what I saw in practice, but a ctrl-c does stop execution of the remote host command. Or is this just a temporary continuation and it exits when it loses the socket or something? Logging output stops immediately so it gets some sort of SIG.
    – notbrain
    Jan 25, 2018 at 0:47
  • Also, I didn't need -t for the simple script linked to above. So there's subtlety to it.
    – notbrain
    Jan 25, 2018 at 0:48
  • 1
    @Brian I'm not sure. I suspect it may have something to do with stdin or so. In my tests remote sleep survives when local ssh exits, but remote cat exits. Each of them exits on Ctrl+C when -t is used. Jan 25, 2018 at 1:10
  • 1
    Pseudo-terminal will not be allocated because stdin is not a terminal. - there should be a way to dial with that in scripts
    – kyb
    Jul 1, 2019 at 15:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.