Automate actions with hooks changedhooks-guide
Nearest release: v2.1.311, published under an hour after upstream edited the page. Shown because the two are within 24 hours of each other. Nothing here says the release caused the edit.
Upstream edited this page at 26 Oct 2026 18:49 UTC, give or take a minute or two: the time comes from Anthropic’s own sitemap rather than from a commit. This site recorded the change at 30 Oct 2026 18:07 UTC.
Upstream edited
Recorded here
Lines+150added
Lines−150removed
From line
85
where the diff opens
First seen
14 Aug 2026
this site's first read of the page
Recorded edits50to this page, all time
The whole hunk
from line 85, old and new numbered
/
from line 85
8585
8686Each tab below uses the platform's native notification command. Add this to `~/.claude/settings.json`:
8787
88**macOS**
89
90```json theme={null}
91{
92 "hooks": {
93 "Notification": [
94 {
95 "matcher": "",
96 "hooks": [
97 {
98 "type": "command",
99 "command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
100 }
101 ]
102 }
103 ]
104 }
105}
106```
107
108> **If no notification appears**
88> [tab] macOS
10989>
110> `osascript` routes notifications through the built-in Script Editor app. If Script Editor doesn't have notification permission, the command fails silently, and macOS won't prompt you to grant it.
111>
112> Run this in Terminal once to make Script Editor appear in your notification settings:
113>
114> ```bash theme={null}
115> osascript -e 'display notification "test"'
90> ```json theme={null}
91> {
92> "hooks": {
93> "Notification": [
94> {
95> "matcher": "",
96> "hooks": [
97> {
98> "type": "command",
99> "command": "osascript -e 'display notification \"Claude Code needs your attention\" with title \"Claude Code\"'"
100> }
101> ]
102> }
103> ]
104> }
105> }
116106> ```
117107>
118> Nothing will appear yet. Open **System Settings > Notifications**, find **Script Editor** in the list, and turn on **Allow Notifications**. Run the command again to confirm the test notification appears.
108> > [accordion] If no notification appears
109> >
110> > `osascript` routes notifications through the built-in Script Editor app. If Script Editor doesn't have notification permission, the command fails silently, and macOS won't prompt you to grant it.
111> >
112> > Run this in Terminal once to make Script Editor appear in your notification settings:
113> >
114> > ```bash theme={null}
115> > osascript -e 'display notification "test"'
116> > ```
117> >
118> > Nothing will appear yet. Open **System Settings > Notifications**, find **Script Editor** in the list, and turn on **Allow Notifications**. Run the command again to confirm the test notification appears.
119119
120**Linux**
121
122```json theme={null}
123{
124 "hooks": {
125 "Notification": [
126 {
127 "matcher": "",
128 "hooks": [
129 {
130 "type": "command",
131 "command": "notify-send 'Claude Code' 'Claude Code needs your attention'"
132 }
133 ]
134 }
135 ]
136 }
137}
138```
139
140> **If no notification appears**
120> [tab] Linux
141121>
142> `notify-send` needs a desktop notification daemon, which headless servers, SSH sessions, and most containers don't have. Test the command directly first:
143>
144> ```bash theme={null}
145> notify-send 'Claude Code' 'test'
122> ```json theme={null}
123> {
124> "hooks": {
125> "Notification": [
126> {
127> "matcher": "",
128> "hooks": [
129> {
130> "type": "command",
131> "command": "notify-send 'Claude Code' 'Claude Code needs your attention'"
132> }
133> ]
134> }
135> ]
136> }
137> }
146138> ```
147139>
148> If the command isn't found, install the `libnotify-bin` package on Debian and Ubuntu, or your distribution's equivalent.
140> > [accordion] If no notification appears
141> >
142> > `notify-send` needs a desktop notification daemon, which headless servers, SSH sessions, and most containers don't have. Test the command directly first:
143> >
144> > ```bash theme={null}
145> > notify-send 'Claude Code' 'test'
146> > ```
147> >
148> > If the command isn't found, install the `libnotify-bin` package on Debian and Ubuntu, or your distribution's equivalent.
149149
150**Windows (PowerShell)**
151
152```json theme={null}
153{
154 "hooks": {
155 "Notification": [
156 {
157 "matcher": "",
158 "hooks": [
159 {
160 "type": "command",
161 "command": "powershell.exe -Command \"[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); [System.Windows.Forms.MessageBox]::Show('Claude Code needs your attention', 'Claude Code')\""
162 }
163 ]
164 }
165 ]
166 }
167}
168```
169
170> **If no dialog appears**
150> [tab] Windows (PowerShell)
171151>
172> This command opens a dialog box rather than a notification in the corner of your screen, so the dialog can open behind your terminal window. Test the command directly in PowerShell first.
152> ```json theme={null}
153> {
154> "hooks": {
155> "Notification": [
156> {
157> "matcher": "",
158> "hooks": [
159> {
160> "type": "command",
161> "command": "powershell.exe -Command \"[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); [System.Windows.Forms.MessageBox]::Show('Claude Code needs your attention', 'Claude Code')\""
162> }
163> ]
164> }
165> ]
166> }
167> }
168> ```
173169>
174> If you run Claude Code inside WSL, `powershell.exe` must be available on your `PATH` through Windows interop.
170> > [accordion] If no dialog appears
171> >
172> > This command opens a dialog box rather than a notification in the corner of your screen, so the dialog can open behind your terminal window. Test the command directly in PowerShell first.
173> >
174> > If you run Claude Code inside WSL, `powershell.exe` must be available on your `PATH` through Windows interop.
175175
176176The empty `matcher` fires on all notification types. To fire only on specific events, set it to one of these values:
177177
from line 701
701701
702702The tabs below show a few more matchers on different event types.
703703
704**Log every Bash command**
704> [tab] Log every Bash command
705>
706> Match only `Bash` tool calls and log each command to a file. The `PostToolUse` event fires after the command completes, so `tool_input.command` contains what ran. The hook receives the event data as JSON on stdin, and `jq -r '.tool_input.command'` extracts only the command string, which `>>` appends to the log file:
707>
708> ```json theme={null}
709> {
710> "hooks": {
711> "PostToolUse": [
712> {
713> "matcher": "Bash",
714> "hooks": [
715> {
716> "type": "command",
717> "command": "jq -r '.tool_input.command' >> ~/.claude/command-log.txt"
718> }
719> ]
720> }
721> ]
722> }
723> }
724> ```
705725
706Match only `Bash` tool calls and log each command to a file. The `PostToolUse` event fires after the command completes, so `tool_input.command` contains what ran. The hook receives the event data as JSON on stdin, and `jq -r '.tool_input.command'` extracts only the command string, which `>>` appends to the log file:
726> [tab] Match MCP tools
727>
728> MCP tools use a different naming convention than built-in tools: `mcp__<server>__<tool>`, where `<server>` is the MCP server name and `<tool>` is the tool it provides. For example, `mcp__github__search_repositories` or `mcp__filesystem__read_file`. Tools from a [plugin-bundled server](https://code.claude.com/docs/en/mcp.md#plugin-provided-mcp-servers) use a scoped server segment instead, such as `mcp__plugin_my-plugin_db__query`. Use a regex matcher to target all tools from a specific server, or match across servers with a pattern like `mcp__.*__write.*`. See [Match MCP tools](https://code.claude.com/docs/en/hooks.md#match-mcp-tools) in the reference for the full list of examples.
729>
730> The command below extracts the tool name from the hook's JSON input with `jq` and writes it to stderr. Writing to stderr keeps stdout clean for JSON output and sends the message to the [debug log](https://code.claude.com/docs/en/hooks.md#debug-hooks):
731>
732> ```json theme={null}
733> {
734> "hooks": {
735> "PreToolUse": [
736> {
737> "matcher": "mcp__github__.*",
738> "hooks": [
739> {
740> "type": "command",
741> "command": "echo \"GitHub tool called: $(jq -r '.tool_name')\" >&2"
742> }
743> ]
744> }
745> ]
746> }
747> }
748> ```
707749
708```json theme={null}
709{
710 "hooks": {
711 "PostToolUse": [
712 {
713 "matcher": "Bash",
714 "hooks": [
715 {
716 "type": "command",
717 "command": "jq -r '.tool_input.command' >> ~/.claude/command-log.txt"
718 }
719 ]
720 }
721 ]
722 }
723}
724```
725
726**Match MCP tools**
727
728MCP tools use a different naming convention than built-in tools: `mcp__<server>__<tool>`, where `<server>` is the MCP server name and `<tool>` is the tool it provides. For example, `mcp__github__search_repositories` or `mcp__filesystem__read_file`. Tools from a [plugin-bundled server](https://code.claude.com/docs/en/mcp.md#plugin-provided-mcp-servers) use a scoped server segment instead, such as `mcp__plugin_my-plugin_db__query`. Use a regex matcher to target all tools from a specific server, or match across servers with a pattern like `mcp__.*__write.*`. See [Match MCP tools](https://code.claude.com/docs/en/hooks.md#match-mcp-tools) in the reference for the full list of examples.
729
730The command below extracts the tool name from the hook's JSON input with `jq` and writes it to stderr. Writing to stderr keeps stdout clean for JSON output and sends the message to the [debug log](https://code.claude.com/docs/en/hooks.md#debug-hooks):
731
732```json theme={null}
733{
734 "hooks": {
735 "PreToolUse": [
736 {
737 "matcher": "mcp__github__.*",
738 "hooks": [
739 {
740 "type": "command",
741 "command": "echo \"GitHub tool called: $(jq -r '.tool_name')\" >&2"
742 }
743 ]
744 }
745 ]
746 }
747}
748```
749
750**Clean up on session end**
751
752The `SessionEnd` event supports matchers on the reason the session ended. This hook only fires on the `clear` reason, set when you run `/clear`, not on normal exits:
753
754```json theme={null}
755{
756 "hooks": {
757 "SessionEnd": [
758 {
759 "matcher": "clear",
760 "hooks": [
761 {
762 "type": "command",
763 "command": "rm -f /tmp/claude-scratch-*.txt"
764 }
765 ]
766 }
767 ]
768 }
769}
770```
750> [tab] Clean up on session end
751>
752> The `SessionEnd` event supports matchers on the reason the session ended. This hook only fires on the `clear` reason, set when you run `/clear`, not on normal exits:
753>
754> ```json theme={null}
755> {
756> "hooks": {
757> "SessionEnd": [
758> {
759> "matcher": "clear",
760> "hooks": [
761> {
762> "type": "command",
763> "command": "rm -f /tmp/claude-scratch-*.txt"
764> }
765> ]
766> }
767> ]
768> }
769> }
770> ```
771771
772772#### Filter by tool name and arguments with the `if` field
773773
No line in this hunk matches that.