forked from bchanot/claude
Only turn end and the moments needing the user should signal. Any other event reaching the hook, such as agent_completed or auth_success, now exits without emitting, so a subagent finishing rings nothing even if the Notification matcher is ignored.
49 lines
1.9 KiB
Bash
Executable File
49 lines
1.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Notification + Stop hook — signal the user through the terminal when
|
|
# Claude needs input (permission, question, idle wait) or has finished
|
|
# responding. Each case gets its own readable label so the toast says
|
|
# which one fired.
|
|
#
|
|
# Runs on the remote (Linux); the only channel that crosses SSH into the
|
|
# VS Code client is the terminal stream. Hooks have no controlling TTY,
|
|
# so the sequence goes through the supported `terminalSequence` JSON
|
|
# output field and Claude Code writes it to the terminal:
|
|
# - BEL x2 (double beep) -> sound, needs VS Code setting
|
|
# accessibility.signals.terminalBell { "sound": "on" } AND a non-zero
|
|
# volume for Code in the Windows volume mixer (BLK-020).
|
|
# - OSC 777 notify -> Windows toast via the client-side extension
|
|
# "Terminal Notification" (wenbopan.vscode-terminal-osc-notifier).
|
|
# A terminal can be deaf to OSC while the bell still rings; test it
|
|
# before attaching a session to it (LRN-148).
|
|
# Both are invisible no-ops in terminals that ignore them.
|
|
set -u
|
|
|
|
payload=$(cat 2>/dev/null)
|
|
read_field() {
|
|
printf '%s' "$payload" | jq -r "$1 // empty" 2>/dev/null \
|
|
| tr -d '\000-\037' | cut -c1-160
|
|
}
|
|
|
|
event=$(read_field '.notification_type')
|
|
[ -n "$event" ] || event=$(read_field '.hook_event_name')
|
|
|
|
case "$event" in
|
|
Stop) label="Finished responding" ;;
|
|
permission_prompt) label="Needs your permission" ;;
|
|
agent_needs_input) label="Asks you a question" ;;
|
|
idle_prompt) label="Waiting for you" ;;
|
|
elicitation_dialog|elicitation_url_dialog) label="Needs your input" ;;
|
|
# anything else (agent_completed, auth_success, quota_*) stays silent:
|
|
# signal only for turn end and moments needing the user.
|
|
*) exit 0 ;;
|
|
esac
|
|
|
|
detail=$(read_field '.message')
|
|
[ -z "$detail" ] || label="${label}: ${detail}"
|
|
|
|
bell=$(printf '\a')
|
|
esc=$(printf '\033')
|
|
seq="${bell}${bell}${esc}]777;notify;Claude Code;${label}${esc}\\"
|
|
jq -cn --arg seq "$seq" '{suppressOutput: true, terminalSequence: $seq}'
|
|
exit 0
|