concurrently exitcode=0 when it receives SIGINT itself (e.g. on ^C)

Author: dimikotCreated Apr 11, 2024Updated May 5, 2025

MacOS and Linux at least.

Sounds like concurrently tool reaction on receiving SIGINT is exiting with code 0 (instead of exiting with e.g. code 130 as the best-practice reaction on Unix processes). This breaks scripts like:

bash
#!/bin/bash
set -e # stop on failed commands
concurrently "one" "two"
echo "still here"

When ^C is pressed in the above script, it still prints "still there", although it should not.

The reason why proper SIGINT handling is important is also described here: https://mywiki.wooledge.org/SignalTrap#When_is_the_signal_handled.3F, section "Special Note On SIGINT and SIGQUIT".

Repro

Console 1:

$ node_modules/.bin/concurrently --version
8.2.2
$ bash -c "node_modules/.bin/concurrently 'exec sleep 1000' 'exec sleep 2000' && echo Exited with exitcode=0"

Console 2:

$ watch -n0.5 'pstree | egrep "sleep" | egrep -v egrep'

CleanShot 2024-04-10 at 17 54 06@2x

Then ^C in console 3:

CleanShot 2024-04-10 at 17 54 33@2x

When ^C is pressed, SIGINT is sent to the entire process group (which is -78069 in this example). I.e. SIGINT is sent to all 4 processes on the screenshot. The bug is that, when concurrently itself receives that SIGINT, it exits with exitcode=0, i.e. it tells the caller that it terminated successfully, although it's not true. According to default unix practices, the process killed by SIGINT should exit with a nonzero exit code (ideally with code=130 which is 128+SIGINT).

We can reproduce the same behavior by not pressing ^C, but by:

  1. Sending SIGINT to concurrently pid itself.
  2. OR - by sending SIGINT to the whole process group, like kill -SIGINT -78069 in the above example.

Interestingly enough, this happens only when receiving SIGINT. On e.g. SIGTERM or SIGHUP it behaves properly.

P.S. There is an ugly work-around for this:

bash -c 'node_modules/.bin/concurrently "exec sleep 1000" "exec sleep 2000" & wait $! && echo Exited with exitcode=0'

Since the shell itself also receives that ^C SIGINT (it's a member of the process group), it fails in wait call, so the message is not printed. But again, this is not a good practice (it is based on a side effect, e.g. it still doesn't help when there is no ^C involved, and only concurrently tool is sent with a SIGINT).

Source: open-cli-tools/concurrently