Claude Code Hooks: Automating macOS Notifications for Task Completion
Claude Code Hooks: Automating macOS Notifications for Task Completion
What are Claude Code Hooks?
Claude Code Hooks are a powerful feature that allows you to execute specific actions based on Claude Code’s behavior. You can automatically run scripts or commands triggered by events such as when the agent completes a response or before and after tool execution.
Setting Up macOS Notifications with Claude Code Hooks
Today, I’ll show you how to display macOS notifications when Claude Code tasks are completed.
The image below shows the actual message on the Claude Code interface.
This implementation builds upon this original article while providing more detailed notification content improvements.
Key Implementation Points
- Uses the
STOPevent as a trigger notify-end.shscript retrieves the latest assistant message from transcript_path- macOS notifications use
osascriptwith sound alerts
Understanding the STOP Event
According to the official documentation:
STOP
Runs when the main Claude Code agent has finished responding. Does not run if the stoppage occurred due to a user interrupt.
This event executes when the Claude Code agent completes its response (but not when interrupted by the user).
Input Data Format
The Input received by the STOP event follows this format:
{
"session_id": "abc123",
"transcript_path": "~/.claude/projects/.../00893aaf-19fa-41d2-8238-13269b9b3ca0.jsonl",
"hook_event_name": "Stop",
"stop_hook_active": true
}💡 Debug Hook Configuration
If you want to examine the actual input data when called, you can set up a hook like this to output to a file:
{
"hooks": {
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "jq -c -r '.' >> ~/.claude/stop-log.txt"
}
]
}
]
}
}Understanding transcript_path Content
The transcript_path contains session interactions saved in JSONL format.
Example:
{
"parentUuid":"d9d1234a-f73f-4460-b1eb-14a8e844df3d",
"isSidechain":false,
"userType":"external",
"cwd":"/Users/m.naka/repos/nakamasato/github-actions",
"sessionId":"b0004de5-676b-4ecb-a49e-23d02a39bfb8",
"version":"1.0.44",
"type":"user",
"message":{
"role":"user",
"content":[
{
"tool_use_id":"toolu_01A7e58yKYcYTYeVYJ8NgAJk",
"type":"tool_result",
"content":"Based on the GitHub releases page, the latest version of ruff-pre-commit is v0.12.2, released on 03 Jul."
}
]
},
"uuid":"f67a28e2-2bce-45f0-8545-3450a9663707",
"timestamp":"2025-07-11T11:06:58.102Z",
"toolUseResult":{
"bytes":293809,
"code":200,
"codeText":"OK",
"result":"Based on the GitHub releases page, the latest version of ruff-pre-commit is v0.12.2, released on 03 Jul.",
"durationMs":3079,
"url":"https://github.com/astral-sh/ruff-pre-commit/releases"
}
}By checking the message.role field, you can see values of either user or assistant. To get the last message when the agent completes, I decided to extract the latest entry with role set to assistant and display it in the notification.
Implementing the Notification Script
Since multiple processing steps are required, I implemented this as a shell script.
#!/bin/bash
# ~/.claude/scripts/notify-end.sh
# Read hook Input data from standard input
INPUT=$(cat)
# Get current session directory name (hooks run in the same directory as the session)
SESSION_DIR=$(basename "$(pwd)")
# Extract transcript_path
TRANSCRIPT_PATH=$(echo "$INPUT" | jq -r '.transcript_path')
# If transcript_path exists, get the latest assistant message
if [ -f "$TRANSCRIPT_PATH" ]; then
# Extract assistant messages from the last 10 lines and get the latest one
# Remove newlines and limit to 60 characters
MSG=$(tail -10 "$TRANSCRIPT_PATH" | \
jq -r 'select(.message.role == "assistant") | .message.content[0].text' | \
tail -1 | \
tr '\n' ' ' | \
cut -c1-60)
# Fallback if no message is retrieved
MSG=${MSG:-"Task completed"}
else
MSG="Task completed"
fi
# Display macOS notification with sound using osascript
osascript -e "display notification \"$MSG\" with title \"ClaudeCode ($SESSION_DIR) Task Done\" sound name \"Glass\""How the Script Works
- Session Information Retrieval: Gets the directory where the hook is executed using
pwdand uses it as the session name - Message Extraction: Extracts messages with
roleset toassistantfrom the last 10 lines of transcript_path and gets the latest one - Notification Display: Uses
osascriptto display notifications with sound effect ("Glass")
This setup allows you to see notifications with sound in the top-right notification area, showing which session completed what task when the agent finishes.
Hooks Configuration File
To make the above script work, add the following configuration to either of the following files:
~/.claude/settings.json(user scope)<project>/.claude/settings.json(project scope)<project>/.claude/settings.local.json(project local scope)
{
"hooks": {
"Stop": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "bash ~/.claude/scripts/notify-end.sh"
}
]
}
]
}
}Conclusion
Using Claude Code Hooks enables you to work efficiently without missing agent task completions. This feature is particularly valuable when you have long-running tasks executing in the background.
However, since getting notifications for every small task can become annoying, it might be better to only notify when agent tasks take a certain amount of time to complete.