the ERR trap is not to run code when the shell itself exits with a non-zero error code, but when any command run by that shell that is not part of a condition (like in if cmd..., or cmd || ......) exits with a non-zero exit status (the same conditions as what causes set -e to exit the shell).
If you want to run code upon exit of the shell with non-zero exit status, you should add a trap on EXIT instead and check $? there:
trap '[ "$?" -eq 0 ] || echo hi' EXIT
Note however that upon a trapped signal, both the signal trap and the EXIT trap would be run, so you may want to do it like:
trap killed_by=INT INT
trap killed_by=TERM TERM
trap '
ret=$?
if [ -n "$killed_by" ]; then
echo >&2 "Ouch! Killed by $killed_by"
exit 1
elif [ "$ret" -ne 0 ]; then
echo >&2 "Died with error code $ret"
fi' EXIT
Or to use exit status like $((signum + 128)) upon signals:
for sig in INT TERM HUP; do
trap "exit $((128 + $(kill -l "$sig")))" "$sig"
done
trap '
ret=$?
[ "$ret" -eq 0 ] || echo >&2 "Bye: $ret"' EXIT
If you want the ERR trap to fire, just run a command with a non-zero exit status like false or test.