Skip to content

Instantly share code, notes, and snippets.

@joechrysler
Created February 16, 2026 14:28
Show Gist options
  • Select an option

  • Save joechrysler/b8f882ed38b4563d2d1f25b100ec1915 to your computer and use it in GitHub Desktop.

Select an option

Save joechrysler/b8f882ed38b4563d2d1f25b100ec1915 to your computer and use it in GitHub Desktop.
claude-notify
#!/usr/bin/env bash
# Claude Code notification hook β€” uses Sonnet to describe what needs attention
# Notifications are delayed 60s and cancelled if the user returns.
#
# Hook configuration (in .claude/settings.json):
# "hooks": {
# "Notification": [
# { "matcher": "permission_prompt",
# "hooks": [{ "type": "command", "command": "claude-notify 🏑 permission" }] },
# { "matcher": "idle_prompt",
# "hooks": [{ "type": "command", "command": "claude-notify 🏑 idle" }] }
# ]
# }
# Requires NOTIFICATIONS_HOST β€” base URL of an ntfy.sh instance (no trailing slash).
# e.g. export NOTIFICATIONS_HOST=http://my-server:8090 (self-hosted)
# export NOTIFICATIONS_HOST=https://ntfy.sh (hosted)
if [ -z "$NOTIFICATIONS_HOST" ]; then
echo "Error: NOTIFICATIONS_HOST is not set." >&2
echo "Set it to the base URL of your ntfy.sh instance (e.g. https://ntfy.sh)" >&2
exit 1
fi
PIDFILE="/tmp/claude-notify-pending.pid"
# Kill any pending delayed notification from a previous invocation
if [ -f "$PIDFILE" ]; then
kill "$(cat "$PIDFILE")" 2>/dev/null
rm -f "$PIDFILE"
fi
EMOJI="${1:-πŸ€–}"
EVENT="${2:-unknown}"
INPUT=$(cat)
CWD=$(echo "$INPUT" | jq -r '.cwd')
DIR=$(basename "$CWD")
TRANSCRIPT=$(echo "$INPUT" | jq -r '.transcript_path')
send_notification() {
local tmux_session tmux_info context instruction
tmux_session=$(tmux display-message -p '#S' 2>/dev/null)
if [ -n "$tmux_session" ]; then
tmux_info="Tmux session: $tmux_session"
else
tmux_info=""
fi
context=$(tail -c 10000 "$TRANSCRIPT" 2>/dev/null | tail -80 || echo 'no context available')
if [ "$EVENT" = "permission" ]; then
instruction="The project is BLOCKED. Briefly say what it's blocked on (e.g. 'needs permission to run tests')."
else
instruction="The project is DONE. Briefly summarize what was accomplished."
fi
printf 'You are a push notification writer for iOS. Rules:
- Write ONLY 1-2 short sentences, no preamble
- NEVER use markdown formatting (no *, **, `, #, etc.) β€” iOS does not render it
- Start with %s then the project name "%s"
- Never say "Claude" β€” the notification source already shows that
- Never say "please return" or "come back" β€” that is obvious
- Be concise and informative, not verbose or redundant
%s
Project directory: %s
%s
Recent session transcript:
%s' \
"$EMOJI" "$DIR" "$instruction" "$DIR" "$tmux_info" "$context" \
| claude -p --model sonnet \
| curl -s -d @- "${NOTIFICATIONS_HOST}/claude"
}
# Delay all notifications: wait 60s, then check if the user came back.
# Skip if the transcript grew (user interacted with Claude) or if tmux
# saw client activity (user is at the keyboard).
TRANSCRIPT_SIZE=$(stat -f%z "$TRANSCRIPT" 2>/dev/null || echo 0)
TMUX_ACTIVITY=$(tmux display-message -p '#{client_activity}' 2>/dev/null)
(
sleep 60
NEW_SIZE=$(stat -f%z "$TRANSCRIPT" 2>/dev/null || echo 0)
NEW_TMUX=$(tmux display-message -p '#{client_activity}' 2>/dev/null)
if [ "$NEW_SIZE" = "$TRANSCRIPT_SIZE" ] && [ "$NEW_TMUX" = "$TMUX_ACTIVITY" ]; then
send_notification
fi
rm -f "$PIDFILE"
) &
echo $! > "$PIDFILE"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment