How to get a notification when a terminal command finishes on Mac
You start a build, switch to something else, and check back five minutes later. It finished four minutes ago. Or it failed after ten seconds. Here are five ways to have the terminal tell you instead, from a one-off command to automatic alerts on your phone.
1. Chain a notification onto the command
macOS can show a notification from the command line with osascript, which ships with every Mac. Put it after your command with ; so it fires whether the command succeeds or fails:
npm run build; osascript -e 'display notification "Build finished" with title "Terminal"'
Use && instead of ; if you only want to hear about success. Better still, include the exit code so the notification tells you whether it worked:
npm test; code=$?; osascript -e "display notification \"Exit code $code\" with title \"Tests finished\""
On recent versions of macOS, these notifications appear under Script Editor. If nothing shows up, open System Settings → Notifications → Script Editor and allow notifications.
Or just make a sound
If you're in the same room, a sound is enough:
make release; say "release finished"
say speaks any text, and afplay /System/Library/Sounds/Glass.aiff plays a system sound.
2. Use the terminal bell
Every terminal understands the bell character. Printing it is the oldest way to ask for attention:
./long-script.sh; printf '\a'
What the bell does depends on your terminal app. In Terminal.app, the profile settings (Settings → Profiles → Advanced) control whether it plays a sound, flashes the window or badges the Dock icon. iTerm2 can turn a bell into a Notification Center alert in its profile settings. Many command-line tools, including test runners and coding agents, ring the bell themselves when they finish or need an answer.
3. Get notified about every slow command automatically
Remembering to add ; say done is the hard part. zsh, the default shell on macOS, can do it for you: record when a command starts, and when it ends, notify you if it took longer than a threshold. Add this to ~/.zshrc:
# Notify when a command takes longer than 30 seconds.
_notify_start() { _cmd_start=$SECONDS; _cmd_name=${1[1,40]} }
_notify_done() {
local code=$?
if [[ -n $_cmd_start ]] && (( SECONDS - _cmd_start > 30 )); then
osascript -e "display notification \"Exit $code after $((SECONDS - _cmd_start))s\" with title \"Command finished\""
fi
unset _cmd_start
}
autoload -Uz add-zsh-hook
add-zsh-hook preexec _notify_start
add-zsh-hook precmd _notify_done
preexec runs just before a command starts and precmd just before the next prompt appears, so the difference is how long the command took. Note that the exit code has to be read first, before anything else in precmd changes it. (Don't call the variable status: in zsh that name is reserved.) Open a new terminal window for it to take effect.
A notification for every slow command is useful at your desk. It doesn't help once you've walked away from the Mac, which is when you need it most.
4. Send it to your phone
To hear about a job when you're not at your Mac, the notification has to leave the Mac. Two common ways:
- A push service with an HTTP API. ntfy is a popular open-source option: install its phone app, subscribe to a topic name that's hard to guess, and send to it with
curl:
Anyone who knows the topic name can read and send to it, so treat the name like a password, or run your own server. Paid services like Pushover work the same way, with an account and an API token../deploy.sh; curl -d "Deploy finished (exit $?)" ntfy.sh/pick-a-long-random-topic - A chat webhook. Slack, Discord and similar tools accept a message posted to a webhook URL. Handy if your team already lives there, but it puts your build output in a chat log.
Both tell you a job finished. Neither lets you do anything about it from your phone. If the build failed, you still have to get back to the Mac to see why.
5. Finished isn't the only thing worth knowing
Many jobs don't fail or finish. They stop and wait: Deploy to production? (y/n), a password prompt, a coding agent asking before it changes a file. Nothing in the tricks above fires, because the command is still running.
That's the case PandaSend is built for. Your terminal sessions run in PandaSend on your Mac, and PandaSend watches each one for a prompt that's been waiting a few seconds, and for the bell or notify codes a tool sends. When it spots one, your phone gets a notification naming the session. Tap it and you're in that same running session, so you can answer the question, read the error or press Ctrl-C. The notification never includes what's on screen, and everything between your phone and your Mac is end-to-end encrypted.
It works with any tool, because it watches the terminal rather than hooking into one program. There's more on this in running coding agents and long jobs from your phone.
Which one should you use?
- At your desk, one command:
; say doneor theosascriptone-liner. - At your desk, every command: the zsh hook in section 3.
- Away from your desk, just to know: a push service like ntfy.
- Away from your desk, and able to act on it: a session you can open from your phone. And make sure the Mac stays awake while you're gone: see how to keep long jobs running when you leave your Mac.