AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL unreviewed MIT Self-run

Mac Notification Claude Code Config

skill-extractumio-extractum-skills-mac-notification-claude-code-config · by extractumio

Configure native macOS notification banners for Claude Code events (Stop, PermissionRequest, Elicitation) via terminal-notifier hooks, for both local macOS and remote Linux/tmux -CC setups bridged through iTerm2 Triggers. Use when the user asks to enable macOS notifications for Claude Code, be pinged when long tasks finish, get alerts for permission or MCP input prompts, or set up the iTerm2 Trig…

No reviews yet
0 installs
33 views
0.0% view→install

Install

$ agentstack add skill-extractumio-extractum-skills-mac-notification-claude-code-config

Open-source listing, not yet scanned by AgentStack. Follow the source repository for install instructions.

Security review

⚠ Flagged

1 finding(s); flagged for manual review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures
  • high Destructive filesystem operation.

What it can access

  • Network access No
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Reliability & compatibility

Not yet reviewed
0 installs to date
no reviews yet
3mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Mac Notification Claude Code Config? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

macOS Native Notifications for Claude Code

Send native macOS notification banners when Claude Code needs your attention — long-running tasks finishing, permission requests, and MCP input prompts. Notifications are suppressed when iTerm2 is already focused, and include the terminal session identity so you know exactly which session needs you.

Works both locally on macOS and remotely on Linux (Ubuntu/Debian) via iTerm2's tmux -CC integration.

When to use this skill

  • User says something like "notify me when Claude finishes", "set up macOS notifications for Claude Code", "I want a banner when permission is needed", or "configure iTerm2 triggers for remote Claude notifications".
  • User is running Claude Code on a remote Linux box via tmux -CC and wants native notifications on their Mac.
  • User wants to customize the minimum task duration, switch to Terminal.app instead of iTerm2, or add a sound to notifications.
  • Troubleshooting: notifications aren't appearing, marker text is visible in terminal, stale /tmp/.claude-task-start-* files, or iTerm2 Trigger isn't firing.

What you get

| Event | Notification | When | |---|---|---| | Stop | Task completed with elapsed time | Only if task took > 2 minutes | | PermissionRequest | Claude is blocked waiting for approval | Always | | Elicitation | MCP tool needs your input | Always |

All notifications are suppressed when iTerm2 is the frontmost app. Clicking a notification brings iTerm2 to focus, even across macOS Spaces/desktops.


Part 1: Local macOS Setup

For Claude Code running directly on your Mac.

Prerequisites

  • macOS (tested on Sequoia)
  • iTerm2
  • Homebrew
  • Claude Code with hooks support
  • Python 3 (pre-installed on macOS)

Installation

1. Install terminal-notifier

brew install terminal-notifier

2. Configure macOS Notification Settings

Go to System Settings > Notifications > terminal-notifier and set:

  • Allow Notifications: On
  • Alert style: Alerts (stays on screen until clicked; use Banners if you prefer auto-dismiss)

Also ensure Do Not Disturb / Focus Mode is off, or terminal-notifier is allowed through your Focus filter.

3. Create the hook scripts

mkdir -p ~/.claude/hooks
~/.claude/hooks/mark-start.sh

Records the timestamp and iTerm2 tab title when the user submits a prompt.

#!/bin/bash
# Writes a start timestamp when user submits a prompt
input=$(cat)
session_id=$(/usr/bin/python3 -c "import sys,json; print(json.load(sys.stdin).get('session_id',''))" /dev/null)
stamp="/tmp/.claude-task-start-${session_id}"

# Clean up stale files older than 24h from crashed sessions
find /tmp -name ".claude-task-start-*" -mmin +1440 -delete 2>/dev/null

# Capture timestamp and iTerm2 tab/window title
tab_title=$(osascript -e '
tell application "iTerm2"
    tell current session of current tab of current window
        return name
    end tell
end tell
' 2>/dev/null)

printf '%s\n%s' "$(date +%s)" "$tab_title" > "$stamp"
~/.claude/hooks/notify.sh

Unified notification handler. Parses JSON from stdin, checks whether a notification is warranted, and sends it via terminal-notifier.

#!/bin/bash
# Unified Claude Code notification hook
# Handles: Stop (long tasks), PermissionRequest, Elicitation

input=$(cat)

eval "$(/usr/bin/python3 -c "
import sys, json
d = json.load(sys.stdin)

event = d.get('hook_event_name', '')
cwd = d.get('cwd', '')
session_id = d.get('session_id', '')
message = d.get('last_assistant_message', '')
tool_name = d.get('tool_name', '')
error = d.get('error', '')
mcp_server = d.get('mcp_server_name', '')

def trunc(s, n=200):
    return s[:n].rsplit(' ', 1)[0] + '...' if len(s) > n else s
message = trunc(message)
error = trunc(error)

print(f'EVENT={repr(event)}')
print(f'CWD={repr(cwd)}')
print(f'SESSION_ID={repr(session_id)}')
print(f'MESSAGE={repr(message)}')
print(f'TOOL_NAME={repr(tool_name)}')
print(f'TOOL_ERROR={repr(error)}')
print(f'MCP_SERVER={repr(mcp_server)}')
" /dev/null)"

project=$(basename "$CWD")
terminal_notifier=$(which terminal-notifier)

# Skip notification if iTerm is the frontmost app (user is already looking at it)
frontmost=$(osascript -e 'tell application "System Events" to get name of first process whose frontmost is true' 2>/dev/null)
[ "$frontmost" = "iTerm2" ] && exit 0

case "$EVENT" in
  Stop)
    stamp="/tmp/.claude-task-start-${SESSION_ID}"
    if [ -f "$stamp" ]; then
      start_time=$(head -1 "$stamp")
      tab_title=$(tail -1 "$stamp")
      now=$(date +%s)
      elapsed=$((now - start_time))
      rm -f "$stamp"
      [ "$elapsed" -lt 120 ] && exit 0
      mins=$((elapsed / 60))
      secs=$((elapsed % 60))
      "$terminal_notifier" \
        -title "Done" \
        -subtitle "${tab_title:-$project} -- ${mins}m ${secs}s" \
        -message "${MESSAGE:-Task completed}" \
        -activate com.googlecode.iterm2
    fi
    ;;

  PermissionRequest|Elicitation)
    # Fetch current tab title live for blocking events
    tab_title=$(osascript -e '
    tell application "iTerm2"
        tell current session of current tab of current window
            return name
        end tell
    end tell
    ' 2>/dev/null)
    if [ "$EVENT" = "PermissionRequest" ]; then
      "$terminal_notifier" \
        -title "Needs Approval" \
        -subtitle "${tab_title:-$project}" \
        -message "Permission needed for: $TOOL_NAME" \
        -activate com.googlecode.iterm2
    else
      "$terminal_notifier" \
        -title "Input Needed" \
        -subtitle "${tab_title:-$project} -- ${MCP_SERVER:-MCP}" \
        -message "Tool $TOOL_NAME needs your input" \
        -activate com.googlecode.iterm2
    fi
    ;;
esac

Make both scripts executable:

chmod +x ~/.claude/hooks/mark-start.sh ~/.claude/hooks/notify.sh

4. Configure Claude Code hooks

Add the following hooks block to your ~/.claude/settings.json:

{
  "hooks": {
    "UserPromptSubmit": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/mark-start.sh"
          }
        ]
      }
    ],
    "Stop": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/notify.sh"
          }
        ]
      }
    ],
    "PermissionRequest": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/notify.sh"
          }
        ]
      }
    ],
    "Elicitation": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/notify.sh"
          }
        ]
      }
    ]
  }
}

No restart required — Claude Code picks up settings changes automatically.


Part 2: Remote Linux Setup (via iTerm2 + tmux -CC)

For Claude Code running on a remote Ubuntu/Debian server, accessed via iTerm2's native tmux integration (tmux -CC).

How It Works

Remote Server (Ubuntu/Debian)              Local Mac (iTerm2)
================================           ================================
Claude Code hook fires
  |
  v
remote-notify.sh
  |
  v
Writes marker string to
tmux pane TTY:
  @@CLAUDE_NOTIFY|type|sub|msg@@
  |
  +--- flows through tmux -CC ---------->  iTerm2 Trigger catches regex
                                             |
                                             v
                                           Runs local-notify.sh
                                             |
                                             v
                                           terminal-notifier shows
                                           native macOS notification

The transport is plain text through the tmux session. No reverse SSH, no extra ports, no daemons.

Prerequisites

On the remote server (Ubuntu 22/24, Debian 12+):

  • Python 3 (apt install python3)
  • tmux
  • Claude Code with hooks support

On your local Mac:

  • Everything from Part 1 (terminal-notifier, iTerm2)
  • iTerm2 Trigger configured (see below)

Remote Installation

1. Create hook scripts on the remote server

mkdir -p ~/.claude/hooks
~/.claude/hooks/mark-start.sh

Records the timestamp, tmux pane title, and pane TTY path when the user submits a prompt.

#!/bin/bash
# Writes a start timestamp when user submits a prompt (remote/Linux version)
# Captures tmux session:window.pane identity and pane title for notifications

input=$(cat)
session_id=$(python3 -c "import sys,json; print(json.load(sys.stdin).get('session_id',''))" /dev/null)
stamp="/tmp/.claude-task-start-${session_id}"

# Clean up stale files older than 24h from crashed sessions
find /tmp -maxdepth 1 -name ".claude-task-start-*" -mmin +1440 -delete 2>/dev/null

# Capture tmux pane title and identity
pane_title=""
pane_tty=""
if [ -n "$TMUX" ]; then
    pane_title=$(tmux display-message -p '#{session_name}:#{window_name}' 2>/dev/null)
    if [ -n "$TMUX_PANE" ]; then
        pane_tty=$(tmux display-message -t "$TMUX_PANE" -p '#{pane_tty}' 2>/dev/null)
    else
        pane_tty=$(tmux display-message -p '#{pane_tty}' 2>/dev/null)
    fi
fi

# Store: line 1 = timestamp, line 2 = pane title, line 3 = pane tty
printf '%s\n%s\n%s' "$(date +%s)" "$pane_title" "$pane_tty" > "$stamp"
~/.claude/hooks/notify.sh

Outputs a marker string to the tmux pane TTY. iTerm2 Trigger on your Mac catches it and calls local-notify.sh.

#!/bin/bash
# Remote Claude Code notification hook (Ubuntu/Debian + tmux -CC -> iTerm2)
#
# Outputs a marker string to the tmux pane TTY.
# iTerm2 Trigger on the local Mac catches the marker and calls local-notify.sh.
#
# Handles: Stop (long tasks), PermissionRequest, Elicitation

input=$(cat)

eval "$(python3 -c "
import sys, json
d = json.load(sys.stdin)

event = d.get('hook_event_name', '')
cwd = d.get('cwd', '')
session_id = d.get('session_id', '')
message = d.get('last_assistant_message', '')
tool_name = d.get('tool_name', '')
mcp_server = d.get('mcp_server_name', '')

def trunc(s, n=200):
    return s[:n].rsplit(' ', 1)[0] + '...' if len(s) > n else s

def clean(s):
    return trunc(s).replace('|', '/').replace('\n', ' ').replace('\r', '')

print(f'EVENT={repr(event)}')
print(f'CWD={repr(cwd)}')
print(f'SESSION_ID={repr(session_id)}')
print(f'MESSAGE={repr(clean(message))}')
print(f'TOOL_NAME={repr(tool_name)}')
print(f'MCP_SERVER={repr(mcp_server)}')
" /dev/null)"

project=$(basename "$CWD")
stamp="/tmp/.claude-task-start-${SESSION_ID}"

# Resolve the TTY to write the marker to
# Priority: stored TTY from mark-start, then current tmux pane, then /dev/tty
resolve_tty() {
    # Try stored TTY from mark-start (line 3 of stamp file)
    if [ -f "$stamp" ]; then
        local stored_tty
        stored_tty=$(sed -n '3p' "$stamp")
        if [ -n "$stored_tty" ] && [ -w "$stored_tty" ]; then
            echo "$stored_tty"
            return
        fi
    fi
    # Try tmux
    if [ -n "$TMUX_PANE" ]; then
        local tty
        tty=$(tmux display-message -t "$TMUX_PANE" -p '#{pane_tty}' 2>/dev/null)
        if [ -n "$tty" ] && [ -w "$tty" ]; then
            echo "$tty"
            return
        fi
    fi
    if [ -n "$TMUX" ]; then
        local tty
        tty=$(tmux display-message -p '#{pane_tty}' 2>/dev/null)
        if [ -n "$tty" ] && [ -w "$tty" ]; then
            echo "$tty"
            return
        fi
    fi
    echo "/dev/tty"
}

notify_tty=$(resolve_tty)

send_marker() {
    local type="$1" subtitle="$2" message="$3"
    printf '@@CLAUDE_NOTIFY|%s|%s|%s@@\n' "$type" "$subtitle" "$message" > "$notify_tty" 2>/dev/null
}

case "$EVENT" in
  Stop)
    if [ -f "$stamp" ]; then
      start_time=$(sed -n '1p' "$stamp")
      pane_title=$(sed -n '2p' "$stamp")
      now=$(date +%s)
      elapsed=$((now - start_time))
      rm -f "$stamp"
      [ "$elapsed" -lt 120 ] && exit 0
      mins=$((elapsed / 60))
      secs=$((elapsed % 60))
      send_marker "DONE" "${pane_title:-$project} -- ${mins}m ${secs}s" "${MESSAGE:-Task completed}"
    fi
    ;;

  PermissionRequest)
    send_marker "APPROVAL" "$project" "Permission needed for: $TOOL_NAME"
    ;;

  Elicitation)
    send_marker "INPUT" "$project -- ${MCP_SERVER:-MCP}" "Tool $TOOL_NAME needs your input"
    ;;
esac

Make both executable:

chmod +x ~/.claude/hooks/mark-start.sh ~/.claude/hooks/notify.sh

2. Configure Claude Code on the remote server

Add the same hooks block to the remote ~/.claude/settings.json:

{
  "hooks": {
    "UserPromptSubmit": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/mark-start.sh"
          }
        ]
      }
    ],
    "Stop": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/notify.sh"
          }
        ]
      }
    ],
    "PermissionRequest": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/notify.sh"
          }
        ]
      }
    ],
    "Elicitation": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/notify.sh"
          }
        ]
      }
    ]
  }
}

3. Create the local receiver script on your Mac

~/.claude/hooks/local-notify.sh

Called by the iTerm2 Trigger when it catches the marker pattern. Runs terminal-notifier locally.

#!/bin/bash
# Called by iTerm2 Trigger when it catches @@CLAUDE_NOTIFY|...|...|...@@
# Arguments: $1 = type (DONE/APPROVAL/INPUT), $2 = subtitle, $3 = message

type="$1"
subtitle="$2"
message="$3"

# Skip notification if iTerm is the frontmost app
frontmost=$(osascript -e 'tell application "System Events" to get name of first process whose frontmost is true' 2>/dev/null)
[ "$frontmost" = "iTerm2" ] && exit 0

terminal_notifier=$(which terminal-notifier)
[ -z "$terminal_notifier" ] && terminal_notifier="/opt/homebrew/bin/terminal-notifier"

case "$type" in
  DONE)
    "$terminal_notifier" \
      -title "Claude Code -- Done" \
      -subtitle "$subtitle" \
      -message "$message" \
      -activate com.googlecode.iterm2
    ;;
  APPROVAL)
    "$terminal_notifier" \
      -title "Claude Code -- Needs Approval" \
      -subtitle "$subtitle" \
      -message "$message" \
      -activate com.googlecode.iterm2
    ;;
  INPUT)
    "$terminal_notifier" \
      -title "Claude Code -- Input Needed" \
      -subtitle "$subtitle" \
      -message "$message" \
      -activate com.googlecode.iterm2
    ;;
esac
chmod +x ~/.claude/hooks/local-notify.sh

4. Configure the iTerm2 Trigger

This is the bridge that catches marker text flowing through tmux and runs the local script.

  1. Open iTerm2 > Settings > Profiles (select your profile) > Advanced
  2. Scroll to Triggers and click Edit
  3. Click + to add a new trigger with these values:

| Field | Value | |---|---| | Regular Expression | @@CLAUDE_NOTIFY\|([^|]*)\|([^|]*)\|([^|]*)@@ | | Action | Run Command... | | Parameters | $HOME/.claude/hooks/local-notify.sh "\1" "\2" "\3" | | Instant | Checked | | Enabled | Checked |

  1. Click Close

Important: The Instant checkbox must be checked so the trigger fires immediately when the marker text appears, without waiting for a newline or cursor movement.


Reference

Hook data available via stdin (JSON)

| Field | Available in | Description | |---|---|---| | session_id | All events | Unique session identifier | | cwd | All events | Current working directory | | hook_event_name | All events | Event type (Stop, PermissionRequest, etc.) | | last_assistant_message | Stop | Claude's final

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.