Skip to content

Instantly share code, notes, and snippets.

@dorelljames
Created August 19, 2026 16:22
Show Gist options
  • Select an option

  • Save dorelljames/378b5fcc2d51f301e67163e82185522a to your computer and use it in GitHub Desktop.

Select an option

Save dorelljames/378b5fcc2d51f301e67163e82185522a to your computer and use it in GitHub Desktop.
zsh - auto-notify via herdr when long shell commands finish
# Notify through herdr when a long-running command finishes.
#
# Why this exists (2026-08-20): herdr only sends lifecycle notifications
# for things it recognizes as agents. A plain `composer qa` or
# `sail artisan test` finishes silently, so switching to another tab
# means polling the old one. This hook times every interactive command
# and raises a herdr notification when a slow one returns. The exit
# code picks the sound: "done" for success, "request" for failure.
#
# Lives in $ZSH_CUSTOM, so oh-my-zsh sources it automatically.
# To uninstall, delete this file.
# HERDR_ENV is exported by herdr into the panes it manages. A normal
# Terminal/iTerm shell never registers these hooks.
[[ "$HERDR_ENV" == 1 ]] || return 0
(( $+commands[herdr] )) || return 0
zmodload zsh/datetime
autoload -Uz add-zsh-hook
# Commands faster than this never notify. Raise it if sub-minute
# builds feel noisy.
: ${HERDR_NOTIFY_MIN_SECONDS:=15}
# Glob patterns matched against the full command line; matches never
# notify. claude and codex already notify through herdr's own agent
# lifecycle, so a second "command finished" for them is pure noise.
# Override by defining this array in .zshrc before oh-my-zsh loads.
typeset -ga HERDR_NOTIFY_IGNORE
(( $#HERDR_NOTIFY_IGNORE )) || HERDR_NOTIFY_IGNORE=(
'claude*' 'codex*' 'herdr*'
)
typeset -g _herdr_notify_cmd=''
typeset -g _herdr_notify_started=0
_herdr_notify_preexec() {
_herdr_notify_cmd=$1
_herdr_notify_started=$EPOCHSECONDS
}
_herdr_notify_precmd() {
local exit_code=$? # must stay the first statement or it gets clobbered
local started=$_herdr_notify_started
local cmd=$_herdr_notify_cmd
_herdr_notify_started=0
_herdr_notify_cmd=''
# started stays 0 when no command ran: empty enter, ctrl-c at the
# prompt. Without this guard those would "inherit" the previous
# command's timer.
(( started > 0 )) || return 0
local elapsed=$(( EPOCHSECONDS - started ))
(( elapsed >= HERDR_NOTIFY_MIN_SECONDS )) || return 0
local pattern
for pattern in $HERDR_NOTIFY_IGNORE; do
[[ $cmd == ${~pattern} ]] && return 0
done
# Mirror what herdr does for agents: a focused tab means the user
# watched the command finish, so a notification is noise. This also
# silences editors and pagers without listing them one by one — an
# editor can only be quit from its own tab, which focuses it. The
# socket round-trip only happens for commands that already ran long.
if [[ -n "$HERDR_TAB_ID" ]] && \
[[ "$(herdr tab get "$HERDR_TAB_ID" 2>/dev/null)" == *'"focused":true'* ]]; then
return 0
fi
# Multi-line and very long commands overflow the notification popup;
# flatten and cap them.
cmd=${cmd//$'\n'/ }
(( ${#cmd} > 120 )) && cmd="${cmd[1,117]}..."
if (( exit_code == 0 )); then
herdr notification show "✓ Finished (${elapsed}s)" \
--body "$cmd" --sound done >/dev/null 2>&1
else
herdr notification show "✗ Failed: exit $exit_code (${elapsed}s)" \
--body "$cmd" --sound request >/dev/null 2>&1
fi
}
add-zsh-hook preexec _herdr_notify_preexec
add-zsh-hook precmd _herdr_notify_precmd
@dorelljames

dorelljames commented Aug 19, 2026 •

Copy link
Copy Markdown
Author

If you're using zsh, place it at ~/.oh-my-zsh/custom/herdr-notify.zsh or you can also translate this in your own shell.

NOTE: exec zsh or open a new terminal after applying...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment