Your old hook already pings you. A mod remembers what it is waiting on. A settings hook in Claude Code can fire a notification when Claude stops or needs you, in every session. A mod does the same, but it keeps track of what happened along the way, so the notification can say "Done: fixed the signup bug" or "Needs input: approve npm install", and remind you once if you miss it. Here is the honest comparison, using the notifications mod we use every day.
New to mods? Start here.
The old ways, and what they already do well
You probably have one of these already. They work, and they are worth knowing before you reach for anything new.
The built-in notification. When Claude finishes or waits on a permission and you seem to be away, Claude Code fires a notification event. Ghostty, Kitty and iTerm2 show it as a desktop notification by default. In other terminals you can set preferredNotifChannel to "terminal_bell" and get a bell instead.1
A settings hook. This is the recipe most people copy: a Stop hook, a Notification hook, or both, in ~/.claude/settings.json, running osascript on a Mac (or terminal-notifier, which makes the banner clickable).
A push to your phone. Swap osascript for a curl to a service like ntfy, and the same hook reaches your phone when you are away from the desk.
To be fair to hooks: they fire for every session, and their input is richer than most recipes use. Every hook gets the working folder (cwd). The Stop hook gets last_assistant_message, the text of Claude's final reply. The PermissionRequest hook gets the tool name and its input, so the exact command. SessionStart gets the session title when one is set.2 So a careful script can already say more than "Claude is done".
The same moment, hook vs mod
Picture three sessions running. You asked one to fix a bug in the signup email, and you went to read Slack. Claude got far, then asked to run npm install. You missed it. Later it finished. This is what lands on your screen (an illustration of the format, not a screenshot).
The copied hook
Which session? Done with what? Did you miss the first one?
The mod
The session, the project, the exact command, a reminder, and what finished.
Same trigger, same moment. The difference is memory: the mod knows which request this was, what is waiting, and whether you already answered.
Why the mod can say more
A settings hook is a command Claude Code starts on an event. Each run is a new process: it sees that one event, then exits. A mod is code that runs inside Claude Code, and all its handlers share the same variables.3 That one difference is what the notification needs.
| What you want | Settings hook | Mod |
|---|---|---|
| Fire on done and on needs input | Yes, every session | Yes, every session |
| Project folder in the title | Yes, from cwd | Yes |
| Claude's final reply | Yes, on Stop | Yes, on turn complete |
| The exact command to approve | Yes, on a permission request | Yes |
| Done vs a question in the reply | Only if your script reads it | Yes, with the AI summary on |
| Your request next to the answer | Needs a temp file between runs | A variable |
| Remind once, cancel when you answer | Needs a background process and a state file | A timer you cancel |
| One-line AI summary | Start your own model call from a script | One call on your own login |
Everything in the hook column is possible. It is just glue: several scripts, files to pass state between them, and a process that sleeps and has to be killed when you answer. In a mod the same logic is a few lines, because the handler that sees the permission prompt and the handler that sees the tool finish can read the same variable.
Reminders need memory too. A notification you miss is worth nothing. The mod sets a timer when Claude asks something, and cancels it the moment the question or permission is answered. If you never answer, you get one more ping. If you do, you get none.
The AI summary is optional. A mod can call a model through the session's own client and credentials.4 One small Haiku call turns your request and Claude's last reply into a short line like "Done: fixed the signup email bug". It runs on your Claude Code login, so it counts toward your usage. Turn it off and you still get the first words of your message and of Claude's reply.
The notifications mod we use every day
We run several Claude Code sessions at once, in the terminal and in the desktop app. Our notifications mod is open source: session-pings on GitHub, with install steps in the README. This is what it does, read straight from its code:
- Titled with the session and project. It uses the session title Claude Code gives it. Until there is one, it names the session in a few words from your first request. The project name comes from the git repository, so a worktree shows the repo's name, not a random folder.
- Done, or needs input. When a turn ends, it reads your request and Claude's final reply and, with the AI summary on, writes "Done: ..." or, if the reply asks you something, "Needs input: ...". With it off, you get "Done:" and the first sentence of the reply. API errors and refusals say "Stopped". Interrupted turns and subagents stay quiet.
- The exact thing waiting. A permission prompt says what it is for: the shell command, "edit" plus the file name, the web address, or the MCP tool. A question Claude asks you shows the question.
- One reminder. If a question or permission is still open after 5 minutes (you can change it, 0 turns it off), it pings once more with "Still needs input". The reminder is cancelled when the tool call ends or a new turn starts.
- Two sounds. One for done, another for needs input, so you can tell them apart without looking.
- Click to go back. With
terminal-notifierinstalled, clicking the banner brings back the app you ran Claude in. Without it, the mod falls back toosascript, and on Linux tonotify-send. - It only watches. Every handler passes the event on unchanged, and the notification is sent in the background, so it never holds up the session.
A mod is code that runs with your permissions, so it is worth checking what one touches before you load it. claude plugin validate lists every event a mod handles and every call it makes.3 For ours:
Read it like a label. It starts processes (the notifier and git), calls a model (the summary), reads two environment variables (to know which app to bring back on click), and sets one timer (the reminder). Nothing else.
When a plain hook is enough
Do not write a mod you do not need. Stay with the built-in notification or a settings hook if:
- You run one session at a time, so "Claude is done" is never ambiguous.
- You sit next to the screen, and a sound or a bell is all you want.
- You use Ghostty, Kitty or iTerm2, where the desktop notification is already on.
- You want a push to your phone, and a one-line
curlin a hook does it. - Your team shares settings files and does not want to review TypeScript.
The mod earns its place when you run several sessions, step away, and keep coming back to find Claude has been waiting on you for twenty minutes.
Build your own
The easy way: ask Claude. In a Claude Code session, say something like "make a mod that sends a Mac notification when you finish or need my approval, says which command, and reminds me once after 5 minutes". Claude uses the built-in plugin-authoring skill, writes the mod, and Claude Code asks whether to enable hot reloading for the session. To keep the mod, copy its folder out of ~/.claude/dev-mods/ and load it with claude --plugin-dir.5
The small way: write it. A mod is three files. This is a minimal version: the permission ping with the exact command, one reminder that is cancelled when you answer, and a "Done" line from the first sentence of Claude's reply. No AI summary, no session title. It passes claude plugin validate on Claude Code 2.1.287, and two claude plugin test tests (the reminder fires, and answering cancels it).
In this sketch any finished tool call clears the reminder, while the full mod tracks the specific call.
Load it with claude --plugin-dir ./ping-sketch. Two notes. The command text is passed to osascript as an argument, not pasted into the script, so quotes in a command cannot break it. And pick a name that does not start with claude-: validate refuses names that look like Anthropic's own.5 If you want mods that guard rather than watch, see rules Claude cannot ignore.
The same idea holds in production: an alert is only useful if it says what broke. That is what we build at Fixter: monitoring for teams that build with coding agents. You send your logs and traces over standard OpenTelemetry, and Fixter finds the issues and bugs in your system and tells you what broke and why, in Slack or email. Then you pull the issue into Claude Code over MCP and fix it.
Key takeaways
- Settings hooks already notify you in every session, and can read the folder, Claude's last reply and the command waiting for approval
- A mod's handlers share memory, so the notification can pair your request with the result and say Done or Needs input
- A mod can remind you once and cancel the reminder the moment you answer
- The one-line AI summary is a small Haiku call on your own login, and you can turn it off
- One session at a time, sitting at the desk? A plain hook or the built-in notification is enough
Frequently asked questions
How do I get a notification when Claude Code is done?
Three ways. Ghostty, Kitty and iTerm2 already show a desktop notification by default. In any terminal you can switch the notification channel to the terminal bell, or add a Stop or Notification hook in settings.json that runs osascript or terminal-notifier. A mod is the fourth way: it runs inside Claude Code and can say what finished, not only that something did.
Can a settings hook show what Claude actually did?
Partly. The Stop hook input includes last_assistant_message, and the permission request hook gets the tool name and its input, so a script can put the reply or the command in the notification. What a settings hook lacks is memory: each run is a new process, so remembering your request, cancelling a reminder when you answer, or summarizing with a model means temp files, background processes and extra wiring.
What does a notification mod do that a hook does not?
It keeps state across events in plain variables. So it can pair your request with Claude's answer, say Done or Needs input, name the exact command waiting for approval, set a reminder and cancel it the moment you answer, and call a small model on your own login for a one-line summary. All of it in one file.
Does the AI summary cost anything?
A mod's model call runs through the session's own client and credentials, so it counts against your plan or API key like any request. A summary is one small Haiku call per finished turn. You can turn it off; the mod we use then falls back to the first words of your message and of Claude's reply.
Do notification mods work in the Claude desktop app?
Yes. Mod hooks run in the terminal and in the Code tab of the Claude Desktop app, and also in the VS Code extension, claude -p and the Agent SDK. A notification mod needs nothing drawn inside Claude Code, so it works wherever hooks run, except WSL sessions in the desktop app, where plugins are not available.
Sources
- Claude Code docs, Configure your terminal (built-in notifications in Ghostty, Kitty and iTerm2; preferredNotifChannel; Notification hooks)
- Claude Code docs, Hooks reference (Stop, Notification, PermissionRequest and SessionStart inputs)
- Claude Code docs, Mods overview (handlers that share variables, where mods run, what a mod can reach, claude plugin validate)
- Claude Code docs, Use the mods API (model calls, timers, processes)
- Claude Code docs, Create a mod (ask Claude, keep a mod, validate, test, reserved names)