Auto-focus the correct terminal pane when Claude Code finishes a task or needs your input. Shows macOS notifications with sound.
- Claude Code CLI installed
jqinstalled (brew install jq)- macOS (uses
osascriptfor notifications)
mkdir -p ~/.claude
cat > ~/.claude/activate-terminal.sh << 'SCRIPT'
#!/bin/bash
detect_terminal() {
[ -n "$GHOSTTY_RESOURCES_DIR" ] && echo "Ghostty" && return
[ -n "$ITERM_SESSION_ID" ] && echo "iTerm2" && return
[ -n "$WEZTERM_PANE" ] && echo "WezTerm" && return
[ -n "$KITTY_PID" ] && echo "kitty" && return
case "${TERM_PROGRAM:-}" in
ghostty) echo "Ghostty" && return ;;
iTerm.app) echo "iTerm2" && return ;;
WezTerm) echo "WezTerm" && return ;;
Apple_Terminal) echo "Terminal" && return ;;
esac
# Inside tmux: walk up process tree to find parent terminal
if [ -n "$TMUX" ]; then
pid=$$
for _ in {1..15}; do
pid=$(ps -p "$pid" -o ppid= 2>/dev/null | tr -d ' ')
[ -z "$pid" ] || [ "$pid" = "1" ] && break
name=$(ps -p "$pid" -o comm= 2>/dev/null)
case "$name" in
*Ghostty*) echo "Ghostty" && return ;;
*iTerm*) echo "iTerm2" && return ;;
*WezTerm*|*wezterm*) echo "WezTerm" && return ;;
*kitty*) echo "kitty" && return ;;
*Terminal*) echo "Terminal" && return ;;
esac
done
fi
echo "Ghostty" # default fallback — change to your terminal
}
activate_iterm2() {
# In tmux -CC mode, match by pane title (synced between tmux and iTerm2)
if [ -n "$TMUX" ]; then
local pane_title
pane_title=$(tmux display-message -p '#{pane_title}' 2>/dev/null)
if [ -n "$pane_title" ]; then
osascript <<EOF
tell application "iTerm2"
activate
repeat with aWindow in windows
repeat with aTab in tabs of aWindow
repeat with aSession in sessions of aTab
if name of aSession is "$pane_title" then
select aWindow
select aTab
select aSession
return
end if
end repeat
end repeat
end repeat
end tell
EOF
return
fi
fi
# Non-tmux: match by session UUID
local session_uuid="${ITERM_SESSION_ID##*:}"
if [ -n "$session_uuid" ]; then
osascript <<EOF
tell application "iTerm2"
activate
repeat with aWindow in windows
repeat with aTab in tabs of aWindow
repeat with aSession in sessions of aTab
if unique id of aSession is "$session_uuid" then
select aWindow
select aTab
select aSession
return
end if
end repeat
end repeat
end repeat
end tell
EOF
else
osascript -e 'tell application "iTerm2" to activate'
fi
}
app=$(detect_terminal)
# For plain tmux (not iTerm2 -CC mode): focus the specific pane
if [ -n "$TMUX" ] && [ "$app" != "iTerm2" ]; then
PANE_ID=$(tmux display-message -p '#{pane_id}' 2>/dev/null)
[ -n "$PANE_ID" ] && tmux select-pane -t "$PANE_ID"
fi
case "$app" in
iTerm2) activate_iterm2 ;;
*) osascript -e "tell application \"$app\" to activate" 2>/dev/null ;;
esac
SCRIPT
chmod +x ~/.claude/activate-terminal.shAdd the hooks block to ~/.claude/settings.json (create if it doesn't exist):
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "msg=$(jq -r '.message // \"Awaiting input\"'); osascript -e \"display notification \\\"$msg\\\" with title \\\"Claude Code\\\" sound name \\\"Ping\\\"\"; bash ~/.claude/activate-terminal.sh"
}
]
}
],
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "project=$(jq -r '.transcript_path | split(\"/\")[-2] // \"unknown\"'); osascript -e \"display notification \\\"Task completed\\\" with title \\\"Claude Code - $project\\\" sound name \\\"Glass\\\"\"; bash ~/.claude/activate-terminal.sh"
}
]
}
]
}
}If you already have a
settings.json, merge thehookskey into it.
| Event | When it fires | What happens |
|---|---|---|
| Notification | Claude needs your input | Ping sound + macOS notification + focuses terminal pane |
| Stop | Claude finished its task | Glass sound + notification with project name + focuses pane |
The activation script:
- Detects which terminal app launched the shell (Ghostty, iTerm2, WezTerm, kitty, Terminal)
- For iTerm2 + tmux -CC mode: matches by pane title to select the exact iTerm2 session/tab
- For plain tmux: uses
tmux select-paneto focus the correct pane - Brings the terminal app to the foreground via AppleScript
Change default terminal: Edit the fallback echo "Ghostty" at the end of detect_terminal() to match your terminal.
Change notification sounds: Replace "Ping" or "Glass" with any sound from /System/Library/Sounds/ (e.g., "Hero", "Submarine", "Tink").
Add more hook events: Other useful events you can hook into:
PermissionRequest— Claude is waiting for a permission approvalSessionStart— inject context when a session beginsPreToolUse/PostToolUse— run commands before/after specific tools
See Claude Code Hooks docs for the full reference.
- Start Claude Code in a tmux pane
- Switch to a different pane or app
- Ask Claude something — when it finishes, your terminal should auto-focus and you'll hear the notification sound