Claude Code's built-in notification fires independently per background task completion. When you run multiple background agents, you get a bell for each one finishing, plus another when the main session completes. If you have 5 agents, that's 6 bells instead of 1.
There's no built-in check for remaining pending tasks before sending the notification.
A single hook script (notify-on-idle.sh) that tracks background agent lifecycle via counter file and only sends a terminal bell when all agents have finished AND the main session is idle.
| Hook Event | Action |
|---|---|
SessionStart |
Reset counter to 0 (handles resume/restart cleanly) |
SubagentStart |
Counter +1 |
SubagentStop |
Counter -1 |
Stop |
If counter ≤ 0 → send bell. Otherwise: suppress. |
tmux-aware: if $TMUX_PANE is set, the bell is written directly to the pane's TTY via #{pane_tty}, so tmux shows the bell flag in the status bar even when you're in a different window.
Note: An earlier version used
tmux send-keysto send the bell, but that injects it as a keystroke into the shell, not as a terminal escape. Writing to the PTY directly is the correct approach.
/config
Set notifications to notifications_disabled.
mkdir -p ~/.claude/hooks
curl -fsSL https://gist.githubusercontent.com/GottZ/34ce27e42dd169515c83dad3aa513db0/raw/notify-on-idle.sh \
-o ~/.claude/hooks/notify-on-idle.sh
chmod +x ~/.claude/hooks/notify-on-idle.shAdd these hook entries (merge with your existing hooks — don't overwrite):
{
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/notify-on-idle.sh"
}
]
}
],
"SubagentStart": [
{
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/notify-on-idle.sh"
}
]
}
],
"SubagentStop": [
{
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/notify-on-idle.sh"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/notify-on-idle.sh"
}
]
}
]
}
}Hooks load at process start. Restart Claude Code for the changes to take effect.
| Scenario | Behavior |
|---|---|
| No background agents | Bell fires normally on every Stop |
| 5 parallel agents | Bell fires once — when the last agent completes and main session stops |
| Session resume after crash | SessionStart resets counter to 0, clean state |
| Agent crashes without SubagentStop | Counter stays positive, no bell until next SessionStart reset |
| tmux | Bell written to pane TTY via #{pane_tty}, shows as window flag |
| No tmux | Falls back to /dev/tty |
bash,python3(for JSON parsing from hook stdin)- Optional:
tmux(for pane-aware bell)
See notify-on-idle.sh below.