feat(hooks): ring terminal on turn end via Stop hook

Notification matcher covers input-needed events only; end of turn had no
signal but idle_prompt, ~60s late. Wire notify-attention.sh on Stop too,
branching on hook_event_name for the message. Signal only: returns
terminalSequence + suppressOutput, never blocks (guard vs BDR-083).

Header documents both client-side prerequisites found in BLK-020.
This commit is contained in:
Bastien Chanot
2026-09-03 00:28:40 +02:00
parent de34e3f167
commit ea9e5c1dab
2 changed files with 33 additions and 7 deletions
+21 -7
View File
@@ -1,21 +1,35 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Notification hook — signal the user through the terminal when Claude # Notification + Stop hook — signal the user through the terminal when
# needs input (permission prompt, question, idle wait). # Claude needs input (permission prompt, question, idle wait) or has
# finished responding.
# #
# Runs on the remote (Linux); the only channel that crosses SSH into the # 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, # VS Code client is the terminal stream. Hooks have no controlling TTY,
# so the sequence goes through the supported `terminalSequence` JSON # so the sequence goes through the supported `terminalSequence` JSON
# output field and Claude Code writes it to the terminal: # output field and Claude Code writes it to the terminal:
# - BEL x2 (double beep) -> sound, needs VS Code setting # - BEL x2 (double beep) -> sound, needs VS Code setting
# accessibility.signals.terminalBell { "sound": "on" } # 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 # - OSC 777 notify -> Windows toast via the client-side extension
# "Terminal Notification" (wenbopan.vscode-terminal-osc-notifier) # "Terminal Notification" (wenbopan.vscode-terminal-osc-notifier).
# The extension only instruments terminals created AFTER it is
# active: install it first, then start or re-attach the session.
# Both are invisible no-ops in terminals that ignore them. # Both are invisible no-ops in terminals that ignore them.
set -u set -u
msg=$(jq -r '.message // .notification_type // empty' 2>/dev/null \ payload=$(cat 2>/dev/null)
| tr -d '\000-\037' | cut -c1-200) read_field() {
[ -n "$msg" ] || msg="Claude Code needs your input" printf '%s' "$payload" | jq -r "$1 // empty" 2>/dev/null \
| tr -d '\000-\037' | cut -c1-200
}
msg=$(read_field '.message // .notification_type')
if [ -z "$msg" ]; then
case "$(read_field '.hook_event_name')" in
Stop) msg="Claude has finished responding" ;;
*) msg="Claude Code needs your input" ;;
esac
fi
bell=$(printf '\a') bell=$(printf '\a')
esc=$(printf '\033') esc=$(printf '\033')
+12
View File
@@ -286,6 +286,18 @@
] ]
} }
], ],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/hooks/notify-attention.sh",
"timeout": 5,
"statusMessage": "Ringing terminal bell..."
}
]
}
],
"UserPromptSubmit": [ "UserPromptSubmit": [
{ {
"hooks": [ "hooks": [