Skip to content

Consolidate Notification / Stop / SubagentStop into native notify.sh#210

Draft
ikuwow wants to merge 2 commits into
mainfrom
feature/notify-script-148
Draft

Consolidate Notification / Stop / SubagentStop into native notify.sh#210
ikuwow wants to merge 2 commits into
mainfrom
feature/notify-script-148

Conversation

@ikuwow

@ikuwow ikuwow commented Jun 7, 2026

Copy link
Copy Markdown
Owner

Refs #148. Lands the A + B + C + E subset of the issue's Phase 1.

Why

The previous Notification / Stop setup wired a single generic noti invocation for every event. That is hard to triage by ear (Stop and a permission prompt sounded similar enough to ignore), invisible across parallel sessions (no repo / branch context), and dependent on a Homebrew package for what is a one-line osascript call on macOS.

The issue cataloged eight independent ideas (A–H). This PR picks the four that compose naturally into a single script and ship today: A (per-event sound), B (repo + branch in title), C (drop noti), E (SubagentStop). D1 / D2 / D3 (idle reminder mechanism), F (theme JSON), G (remote channel), H (Kansai-ben voice) stay parked in #148 for a follow-up.

What

  • claude/hooks/notify.sh (shellcheck-clean, set -u, ~70 LoC). Reads the hook JSON from stdin, picks a /System/Library/Sounds/*.aiff per event-or-matcher, builds a title Claude Code · <repo> (<branch>), then afplay + osascript. Notification matcher branching is content-based: the message field of the stdin JSON is checked for permission / idle substrings to choose between Submarine / Ping / Blow.
  • claude/settings.json: replaces the two noti commands with bash ~/.claude/hooks/notify.sh, plus a new SubagentStop matcher wired to the same script. block_excuse_phrases.py stays as the first Stop hook so the AIRULES guard still fires.

Notes

  • macOS-only by design — afplay and osascript are not available on Linux. The hook is best-effort: missing tools cause silent no-ops via || true and 2>/dev/null, not failures.
  • The branch lookup tolerates being invoked from outside a git repo (git rev-parse returns non-zero, branch stays empty, title falls back to Claude Code · <repo>).
  • noti stays installed via the existing Brewfile for other consumers; this PR only removes its use from the Claude Code hooks. Dropping it from Brewfile is a separate decision.

Verification

  • shellcheck claude/hooks/notify.sh clean.
  • jq . claude/settings.json validates.
  • Direct invocation echo '{"hook_event_name":"Stop","cwd":"/Users/ikuwow/dotfiles","message":""}' | bash claude/hooks/notify.sh exits 0 — Stop path wired.
  • Direct invocation with "hook_event_name":"Notification","message":"Claude needs your permission to use Bash" exits 0 — Notification permission_prompt path wired (Submarine branch selected by content match).
  • Direct invocation with "hook_event_name":"SubagentStop" exits 0 — SubagentStop path wired (Pop sound).
  • Real session triggers: permission prompt sound (Submarine), Stop chime (Glass), SubagentStop (Pop) — confirmed audibly in a live session. (Sound playback is async, the exit-0 tests above confirm the wiring; the audible-distinction check is left as user-side because it depends on perception.)
  • block_excuse_phrases.py still fires before notify on a 断り書き violation — settings.json keeps block_excuse_phrases.py first in the Stop hook array; confirmed by reading the diff.

ikuwow and others added 2 commits June 7, 2026 15:08
Implements part of #148 Phase 1 (A + B + C + E):
- A: per-event sound differentiation (Stop=Glass, SubagentStop=Pop,
     Notification permission_prompt=Submarine, idle_prompt=Ping,
     other=Blow)
- B: repo + branch in notification title for parallel-session
     disambiguation
- C: drops the noti dependency in favor of native afplay + osascript
- E: adds SubagentStop hook

D1 (Notification matcher idle_prompt branching is a content match
inside the script, not a separate settings.json matcher) and H
(Kansai-ben voice) are out of scope for this PR — both are tracked
in #148 for follow-up.

Refs #148.

Co-authored-by: Claude Opus 4.6 <[email protected]>
The earlier `[ -d "$cwd/.git" ] || git rev-parse` had a precedence
shape that read as a redundant fast-path. The rev-parse alone covers
worktrees, subdirectories, and empty cwd cases.

Co-authored-by: Claude Opus 4.6 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant