Claude Code Hooks for Fun and Focus
My attention wanders. A lot. I’ll kick off a Claude Code session, switch to check on something else, and 20 minutes later realize I’ve got a forlorn Claude tab that’s been waiting for approval for …19 minutes. A notification beep doesn’t help much—it starts a quick game of “which tab was that?”. Not a huge...
y attention wanders. A lot. I’ll kick off a Claude Code session, switch to check on something else, and 20 minutes later realize I’ve got a forlorn Claude tab that’s been waiting for approval for …19 minutes. A notification beep doesn’t help much—it starts a quick game of “which tab was that?”. Not a huge time sink, since it takes only a moment to find the tab. But those moments build up over the day, not only in time spent, but in mental fatigue.
So, inspired by this nice blog post, I set up Claude Code hooks to tell me what’s happening, with a fun twist.
The Setup
Claude Code supports hooks—shell scripts that run on events like “needs permission” or “task finished.” Hooks receive JSON on stdin with context about the session.
On macOS, the say command does text-to-speech, and the voices have come a long way in quality from where they were years ago.
Combine the hooks and the voices:
say "Claude needs your permission to edit server.ts"
Now I hear what needs attention, not just that something does.
The Fun Part: Session Identities
I have multiple terminals. Sometimes too many. Sometimes way too many. I wanted a way to hear which is calling me. Rather than try to announce paths (since my sessions are often in the same project directory) or worse, listen to session UUIDs being read aloud, I employed a little hashing logic for the sake of cuteness.
My hook script hashes the session ID (stable across hook calls) to reliably map to one of a set of emoji identities:
EMOJIS=("🍌:Banana" "🦊:Fox" "🌮:Taco" "🚀:Rocket" ...)
# MD5 hash the session ID for uniform randomnessHASH=$(echo -n "$SESSION_ID" | md5 -q)# Map the hash to an emoji index:# - Take first 8 hex chars and convert to a number# - Mod by emoji count to get index 0-24# - MD5 distributes inputs uniformly, so each emoji# has roughly equal chance of being selectedINDEX=$((16#${HASH:0:8} % ${#EMOJIS[@]}))
</div>
</div>
Now I hear “Banana needs your permission” or “Octopus is done.” Each session gets a consistent identity. We’ll see if naturally build a mental map: “Right, Banana is the API refactor, Octopus is the docs.” I don’t intend on doing this consciously, but letting my second brain do it.
Marking the tab
It’s not enough to hear the sound. I’ve got to be able to see which session is which, so the hook sets the terminal title via /dev/tty:
echo -ne "\033]0;🍌 Banana - Claude Code\007" > /dev/tty 2>/dev/null
</div>
</div>
Here’s a bit from Claude on what /dev/tty is, in case you (like me) didn’t know:
/dev/ttyis a special file that always points to the actual terminal the process is connected to, bypassing any stdout redirection.
This means that, even though the hook scripts are run in a sub-process, they can send a command to change the title of the terminal directly to the tab that called them.
The Scripts
speak-notification.sh (runs when Claude needs input):
- Extracts the message and session ID from stdin JSON
- Hashes session → emoji identity
- Sets terminal title
- Speaks the message passed into the hook, prefixed by the session emoji: “Banana needs your permission to run npm install”
speak-done.sh (runs when Claude finishes):
- Same identity mapping
- Says: “Banana is done”
Does It Help?
I just set this up today, so the jury’s out. But the first few times hearing “Cupcake is “done pulled me back to the right terminal instantly. That’s promising.
The code is simple—two ~50 line bash scripts. If your attention wanders like mine, might be worth a try.
Installing
1. Create the hooks directory
mkdir -p ~/.claude/hooks
2. Create ~/.claude/hooks/speak-notification.sh
#!/bin/bash
# Speaks notifications with a fun session identity (emoji + name)
# 25 fun emoji identities
EMOJIS=(
"🍌:Banana"
"🦊:Fox"
"🌮:Taco"
"🚀:Rocket"
"🦄:Unicorn"
"🐙:Octopus"
"🍕:Pizza"
"🌈:Rainbow"
"🦋:Butterfly"
"🍩:Donut"
"🐳:Whale"
"🌻:Sunflower"
"🎸:Guitar"
"🦖:T-Rex"
"🍪:Cookie"
"🐨:Koala"
"🌶️:Pepper"
"🦜:Parrot"
"🧁:Cupcake"
"🐸:Frog"
"🍋:Lemon"
"🦉:Owl"
"🎪:Circus"
"🐝:Bee"
"🍄:Mushroom"
)
# Read JSON from stdin
INPUT=$(cat)
MESSAGE=$(echo "$INPUT" | jq -r '.message // "needs attention"')
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "default"')
# MD5 hash the session ID for uniform randomness
HASH=$(echo -n "$SESSION_ID" | md5 -q)
# Map the hash to an emoji index:
# - Take first 8 hex chars and convert to a number
# - Mod by emoji count to get index 0-24
# - MD5 distributes inputs uniformly, so each emoji
# has roughly equal chance of being selected
HASH_NUM=$((16#${HASH:0:8}))
INDEX=$((HASH_NUM % ${#EMOJIS[@]}))
# Split emoji and name
EMOJI="${EMOJIS[$INDEX]%%:*}"
NAME="${EMOJIS[$INDEX]##*:}"
# Set terminal title (write directly to tty to bypass stdout capture)
echo -ne "\033]0;${EMOJI} ${NAME} - Claude Code\007" > /dev/tty 2>/dev/null
# Speak with the identity name
# Transform message: "Claude needs your permission" -> "Banana needs your permission"
SPOKEN_MESSAGE="${MESSAGE/Claude/$NAME}"
say "$SPOKEN_MESSAGE"
3. Create ~/.claude/hooks/speak-done.sh
#!/bin/bash
# Speaks "done" with fun session identity
EMOJIS=(
"🍌:Banana"
"🦊:Fox"
"🌮:Taco"
"🚀:Rocket"
"🦄:Unicorn"
"🐙:Octopus"
"🍕:Pizza"
"🌈:Rainbow"
"🦋:Butterfly"
"🍩:Donut"
"🐳:Whale"
"🌻:Sunflower"
"🎸:Guitar"
"🦖:T-Rex"
"🍪:Cookie"
"🐨:Koala"
"🌶️:Pepper"
"🦜:Parrot"
"🧁:Cupcake"
"🐸:Frog"
"🍋:Lemon"
"🦉:Owl"
"🎪:Circus"
"🐝:Bee"
"🍄:Mushroom"
)
INPUT=$(cat)
SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // "default"')
# MD5 hash the session ID for uniform randomness
HASH=$(echo -n "$SESSION_ID" | md5 -q)
# Map the hash to an emoji index
HASH_NUM=$((16#${HASH:0:8}))
INDEX=$((HASH_NUM % ${#EMOJIS[@]}))
EMOJI="${EMOJIS[$INDEX]%%:*}"
NAME="${EMOJIS[$INDEX]##*:}"
# Update terminal title (write directly to tty to bypass stdout capture)
echo -ne "\033]0;${EMOJI} ${NAME} - Done!\007" > /dev/tty 2>/dev/null
say "${NAME} is done"
### 4. Make them executable
chmod +x ~/.claude/hooks/speak-notification.sh
chmod +x ~/.claude/hooks/speak-done.sh
### 5. Add hooks to ~/.claude/settings.json
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/speak-notification.sh"
}
]
}
],
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "~/.claude/hooks/speak-done.sh"
}
]
}
]
}
}
If you already have a settings.json, merge the hooks section into it.
Requirements
- macOS (uses
sayandmd5 -q) jqfor JSON parsing (brew install jqif needed)
Attribution
Authored in collaboration with Claude Sonnet 4.5
Did you enjoy this article?
Recommend it — Standard Reader surfaces well-loved writing to more readers across the network.