desk-notify
A Claude Code Mod that sends a desktop notification when a question or a plan waits for your answer, and when a turn ends or fails, so a session in another window does not wait unseen.
What it does
- When the model calls
AskUserQuestion, the mod sendsQuestion awaiting your answer, before the question blocks on you. - When the model calls
ExitPlanMode, it sendsPlan awaiting your approval, before the approval blocks on you. - When a main-loop turn ends (
Stop), it sendsTurn finished. A subagent's end isSubagentStopand sends nothing. - When an API error ends a turn (
StopFailure), it sendsTurn failedwith the first line of the error, cut at 60 characters and without its markdown marks; with no error text, the turn's last words stand in. - Every notification carries the subtitle
Claude Codeand the project name: the primary repository in a git worktree too, else the git root, else the session's directory. The name is read once at the session's start, so a shellcddoes not rename it.
The notification command returns at once and is killed after 5 seconds, so a hung notification daemon holds up no tool call:
| Desktop | Command |
|---|---|
| macOS | osascript -e 'display notification ...' |
| Linux | notify-send <title> <subtitle and body> (it has no subtitle field) |
| Windows | a PowerShell toast, which never waits on a click |
The desktop is read once per session: OS=Windows_NT names Windows, else uname -s names Darwin or Linux. On any other system the mod says so once and sends nothing. A notification command that fails or is missing is reported once as a transcript line, until another failure replaces it.
In the live check on 2.1.282 a question the model asked with AskUserQuestion ran osascript with exit 0 before the question showed.
Command
/desk-notify the desktop and each event's setting
/desk-notify ask on | off a question that waits for your answer
/desk-notify plan on | off a plan that waits for your approval
/desk-notify stop on | off a turn that ended or failed
Each event is on by default and its setting is kept across sessions.
Install
claude plugin marketplace add KilimcininKorOglu/claude-code-mods
claude plugin install desk-notify@kilimcininkoroglu-mods
Function hooks are early access. Nothing loads without the flag. To keep it on, add this to ~/.claude/settings.json:
{ "env": { "CLAUDE_CODE_ENABLE_FUNCTION_HOOKS": "1" } }
After installing
- Restart Claude Code.
- On macOS, when no notification shows, check System Settings > Notifications for the app
osascriptnotifications appear under. On Linux, installnotify-send(libnotify). - Remove any hook of your own that already sends these notifications, or each event notifies twice.
What it can reach
Validated with claude plugin validate on Claude Code 2.1.282:
❯ ./register.ts hooks: session.start, command.run{command=desk-notify}, tool.call{tool=/"^AskUserQuestion$"/}, tool.call{tool=/"^ExitPlanMode$"/}, classic.Stop, classic.StopFailure
❯ ./register.ts calls: $.command.register, $.env.get (via readPlatform), $.process.run (via gitOut, readPlatform, send), $.session.cwd (via readProject), $.store.get (via readSettings), $.store.set (via runCommand), $.ui.log
❯ ./register.ts env reads: OS
Reach L2, runs processes.
1. Reads: the OS variable, the tool name of each call, and a failed turn's error text and last assistant message
2. Runs: uname -s and two git rev-parse calls once per session; one osascript, notify-send or powershell.exe per notification
3. Sends: a desktop notification with a fixed title, the project name and, for a failed turn, 60 characters of the error; nothing to the model and nothing off the machine
4. Persists: in $.store, the on/off setting of each event
5. Hostile input: the project name and the error text are escaped for the AppleScript and PowerShell string literals and pass to notify-send as one argv entry, so neither can run a command
Limits
- The macOS notification has no icon of its own:
display notificationtakes no icon argument. - Whether a turn you interrupt raises
Stopis not measured. - The Linux and Windows commands are not measured live.
Development
make install # eslint, typescript-eslint, typescript
make lint # complexity limit 10, fails the build above it
make typecheck # needs .claude/types/ from /plugin-types
make validate
make test # claude plugin test