Created
February 16, 2026 14:28
-
-
Save joechrysler/b8f882ed38b4563d2d1f25b100ec1915 to your computer and use it in GitHub Desktop.
claude-notify
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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