Skip to content

feat: add macOS desktop notification Stop hook#846

Merged
affaan-m merged 3 commits into
affaan-m:mainfrom
pythonstrup:feat/desktop-notify-hook
Mar 25, 2026
Merged

feat: add macOS desktop notification Stop hook#846
affaan-m merged 3 commits into
affaan-m:mainfrom
pythonstrup:feat/desktop-notify-hook

Conversation

@pythonstrup

@pythonstrup pythonstrup commented Mar 24, 2026

Copy link
Copy Markdown
Contributor

What Changed

Add a new Stop hook (desktop-notify.js) that sends a native macOS desktop notification with the task summary when Claude finishes responding.

  • New file: scripts/hooks/desktop-notify.js — Extracts the first line of last_assistant_message (truncated to 100 chars) and sends it via osascript
  • Modified: hooks/hooks.json — Registered the hook in the Stop array with async: true and timeout: 5
  • Modified: hooks/README.md — Added entry to the Lifecycle Hooks table

Why This Change

There is no built-in way to know when Claude Code finishes a task if you switch away from the terminal. This hook provides Codex-style desktop notifications so users can multitask without constantly checking the terminal.

Testing Done

  • Manual testing completed
    • Piped JSON with last_assistant_message to the script — macOS notification appeared with correct summary
    • Piped empty JSON {} — notification appeared with fallback text "Done"
    • Verified stdout passthrough (hook returns raw input unchanged)
  • Automated tests pass locally (node tests/run-all.js)
  • Edge cases considered and tested
    • Empty/missing last_assistant_message → fallback to "Done"
    • Non-macOS platforms → silent exit (no error)
    • Multiline messages → only first non-empty line used

Type of Change

  • feat: New feature

Security & Quality Checklist

  • No secrets or API keys committed (ghp_, sk-, AKIA, xoxb, xoxp patterns checked)
  • JSON files validate cleanly
  • Shell scripts pass shellcheck (if applicable) — N/A (Node.js script)
  • Pre-commit hooks pass locally (if configured)
  • No sensitive data exposed in logs or output
  • Follows conventional commits format
  • Uses spawnSync with argument array (not shell string) to prevent command injection
  • JSON.stringify used for osascript arguments to safely escape user content

Documentation

  • Updated relevant documentation (hooks/README.md)
  • Added comments for complex logic
  • README updated (if needed) — N/A, hook is auto-discovered via hooks.json

Summary by cubic

Add a Stop hook that sends a native macOS notification with a short task summary when Claude finishes responding. Uses safe AppleScript escaping, logs failures, and fits within the hook timeout for reliable delivery.

  • New Features

    • Added scripts/hooks/desktop-notify.js: takes the first non-empty line of last_assistant_message (≤100 chars), sends via osascript, supports run-with-flags fast path, and passes stdin through unchanged; no-op on non-macOS.
    • Registered in hooks/hooks.json for standard and strict with async: true and timeout: 10; hooks/README.md documents the hook.
  • Bug Fixes

    • AppleScript-safe quoting (curly-quote substitution, strip backslashes) to prevent broken notifications.
    • Log osascript failures and use a 5000ms spawnSync timeout to stay within the 10s hook window.

Written for commit f6b1048. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Adds native macOS desktop notifications on task completion that show concise, auto-generated task summaries (trimmed for readability). Notifications are delivered asynchronously at task stop, do not block operation, and are no-ops on non-macOS platforms; failures are handled silently.
  • Documentation

    • Updated hooks documentation to describe the new desktop notification hook.

@ecc-tools

ecc-tools Bot commented Mar 24, 2026

Copy link
Copy Markdown
Contributor

Analyzing 5000 commits...

@ecc-tools

ecc-tools Bot commented Mar 24, 2026

Copy link
Copy Markdown
Contributor

❌ Analysis Failed

Not Found - https://docs.github.com/rest/git/refs#get-a-reference

Troubleshooting
Cause Resolution
Large repository Analysis may timeout on repos with extensive history
API rate limits Wait 15 minutes before retrying
Network issues Queue timeout is 15 minutes; retry may succeed
Permissions Verify app has Contents: Read access

Retry: /ecc-tools analyze


Report Issue | ECC Tools

@coderabbitai

coderabbitai Bot commented Mar 24, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f87be391-d61f-4333-b3c4-1dd9394a123a

📥 Commits

Reviewing files that changed from the base of the PR and between c21118e and f6b1048.

📒 Files selected for processing (3)
  • hooks/README.md
  • hooks/hooks.json
  • scripts/hooks/desktop-notify.js
✅ Files skipped from review due to trivial changes (2)
  • hooks/hooks.json
  • hooks/README.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • scripts/hooks/desktop-notify.js

📝 Walkthrough

Walkthrough

Adds a Stop-phase macOS desktop notification: documentation entry, a new Stop hook registration in hooks/hooks.json invoking scripts/hooks/desktop-notify.js, and a new Node.js hook script that posts a truncated task summary via AppleScript on macOS (no-op on other platforms).

Changes

Cohort / File(s) Summary
Documentation
hooks/README.md
Added "Desktop notify" row to the lifecycle/Stop hook table describing the new macOS notification hook (marked "standard+").
Hook Configuration
hooks/hooks.json
Registered a new Stop hook for matcher * that runs asynchronously via run-with-flags.js with flags "standard,strict" and timeout: 10.
Notification Script
scripts/hooks/desktop-notify.js
New module exporting run(raw); parses trimmed JSON (or {}), extracts first non-empty line from input.last_assistant_message, truncates to a MAX_BODY_LENGTH with ellipsis, sanitizes quotes/backslashes, synchronously spawns osascript to display a notification on macOS, logs errors, always returns raw. Supports direct stdin/stdout execution (reads up to 1MB).

Sequence Diagram

sequenceDiagram
    participant HookSystem as Hook System
    participant Script as desktop-notify.js
    participant macOS as osascript / macOS

    HookSystem->>Script: invoke run(raw) on Stop
    Script->>Script: parse raw (JSON or {})
    Script->>Script: extract first non-empty line\nsanitize and truncate to MAX_BODY_LENGTH
    alt platform is macOS
        Script->>macOS: run osascript (display notification)
        macOS->>macOS: show desktop notification
    else
        Script->>Script: skip notification (no-op)
    end
    Script->>HookSystem: return raw unchanged
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I nibbled bytes and shaped a note so spry,

A tiny summary popped up in the sky.
AppleScript twinkle, a brief carrot chime,
The rabbit hops off — your task — sublime! 🥕🔔

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main feature being added: a macOS desktop notification Stop hook. It accurately reflects the primary purpose of the changeset.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented Mar 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a Stop hook (scripts/hooks/desktop-notify.js) that fires a native macOS desktop notification with a short task summary when Claude finishes responding — a useful quality-of-life addition for users who multitask away from the terminal.

All three concerns raised in the previous review round have been resolved in this revision:

  • AppleScript escapingJSON.stringify is gone; double quotes are now replaced with curly quotes (\u201C) and backslashes are stripped before the string is embedded in the display notification command.
  • Timeout headroom — The hook's outer timeout is now 10 s while spawnSync is capped at 5000 ms, giving ample room for Node.js startup overhead.
  • spawnSync result — The return value is now checked for both result.error and a non-zero result.status, with failures logged to stderr.

Remaining minor note:

  • When osascript is killed by a signal (rather than timing out or returning a non-zero exit code), result.status is null and result.error is not set; the log currently reads exit null instead of something like killed by SIGTERM.

Confidence Score: 5/5

  • This PR is safe to merge; all previously raised concerns are addressed and the remaining note is a minor log-message improvement.
  • All three substantive issues from the prior review round (AppleScript escaping, timeout headroom, unhandled spawnSync result) have been fixed. The hook runs asynchronously, cannot block Claude, and exits silently on non-macOS. The only open item is a cosmetic exit null log message when osascript is killed by a signal — not a reliability or security concern.
  • No files require special attention.

Important Files Changed

Filename Overview
scripts/hooks/desktop-notify.js New Stop hook that sends a macOS desktop notification via osascript. AppleScript escaping, error checking, and timeout headroom are all properly handled. One minor: signal-killed osascript produces a confusing "exit null" log message.
hooks/hooks.json Registers the desktop-notify hook in the Stop array with async:true and timeout:10. Entry follows the established pattern of other Stop hooks in the file.
hooks/README.md Adds a single row to the Lifecycle Hooks table for the new desktop-notify hook. Documentation is accurate and matches the implementation.

Sequence Diagram

sequenceDiagram
    participant CC as Claude Code
    participant RWF as run-with-flags.js
    participant DN as desktop-notify.js
    participant OS as osascript

    CC->>RWF: Stop hook fired (stdin JSON)
    RWF->>DN: run(raw)
    DN->>DN: isMacOS check
    alt not macOS
        DN-->>RWF: return raw (no-op)
    else macOS
        DN->>DN: JSON.parse(raw)
        DN->>DN: extractSummary(last_assistant_message)
        DN->>OS: spawnSync('osascript', ['-e', script], {timeout:5000})
        alt success
            OS-->>DN: exit 0
        else failure
            OS-->>DN: non-zero / error / signal
            DN->>DN: log error to stderr
        end
        DN-->>RWF: return raw
    end
    RWF-->>CC: stdout passthrough
Loading

Reviews (4): Last reviewed commit: "fix: add spawnSync error logging and res..." | Re-trigger Greptile

Comment thread scripts/hooks/desktop-notify.js
Comment thread scripts/hooks/desktop-notify.js Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
scripts/hooks/desktop-notify.js (1)

50-52: Consider tracking cross-platform support.

The TODO comments indicate Windows and Linux support is planned. Based on learnings about ensuring cross-platform compatibility, consider opening an issue to track this work so it doesn't get lost.

Would you like me to help draft an issue to track Windows (PowerShell BurntToast or native toast) and Linux (notify-send) support?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/hooks/desktop-notify.js` around lines 50 - 52, Add a tracked issue
for cross-platform desktop notifications referenced by the TODOs in
scripts/hooks/desktop-notify.js (functions notifyWindows and notifyLinux); the
issue should describe implementing notifyWindows (PowerShell BurntToast or
native toast API), notifyLinux (notify-send or libnotify), a clear
API/abstraction in the existing notify function, fallback behavior for
unsupported platforms, testing requirements, and acceptance criteria so this
planned work isn't lost.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@scripts/hooks/desktop-notify.js`:
- Around line 50-52: Add a tracked issue for cross-platform desktop
notifications referenced by the TODOs in scripts/hooks/desktop-notify.js
(functions notifyWindows and notifyLinux); the issue should describe
implementing notifyWindows (PowerShell BurntToast or native toast API),
notifyLinux (notify-send or libnotify), a clear API/abstraction in the existing
notify function, fallback behavior for unsupported platforms, testing
requirements, and acceptance criteria so this planned work isn't lost.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1007207f-cc8e-4be9-931d-84b42be8d921

📥 Commits

Reviewing files that changed from the base of the PR and between df4f2df and 959f7bb.

📒 Files selected for processing (3)
  • hooks/README.md
  • hooks/hooks.json
  • scripts/hooks/desktop-notify.js

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 3 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="hooks/hooks.json">

<violation number="1" location="hooks/hooks.json:298">
P2: New Stop desktop notification hook exposes assistant summary text through macOS notifications by default, creating avoidable privacy/data-leak risk.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread hooks/hooks.json
"hooks": [
{
"type": "command",
"command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"stop:desktop-notify\" \"scripts/hooks/desktop-notify.js\" \"standard,strict\"",

@cubic-dev-ai cubic-dev-ai Bot Mar 24, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: New Stop desktop notification hook exposes assistant summary text through macOS notifications by default, creating avoidable privacy/data-leak risk.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At hooks/hooks.json, line 298:

<comment>New Stop desktop notification hook exposes assistant summary text through macOS notifications by default, creating avoidable privacy/data-leak risk.</comment>

<file context>
@@ -289,6 +289,18 @@
+        "hooks": [
+          {
+            "type": "command",
+            "command": "node \"${CLAUDE_PLUGIN_ROOT}/scripts/hooks/run-with-flags.js\" \"stop:desktop-notify\" \"scripts/hooks/desktop-notify.js\" \"standard,strict\"",
+            "async": true,
+            "timeout": 5
</file context>
Fix with Cubic

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is intentional — the hook is opt-in by design (only active on standard and strict profiles, not minimal). Users who install the plugin explicitly want task completion notifications. They can disable it anytime via ECC_DISABLED_HOOKS=stop:desktop-notify. The notification only shows the first line of the response (truncated to 100 chars), not the full conversation.

@ecc-tools

ecc-tools Bot commented Mar 24, 2026

Copy link
Copy Markdown
Contributor

Analyzing 5000 commits...

@ecc-tools

ecc-tools Bot commented Mar 24, 2026

Copy link
Copy Markdown
Contributor

❌ Analysis Failed

Not Found - https://docs.github.com/rest/git/refs#get-a-reference

Troubleshooting
Cause Resolution
Large repository Analysis may timeout on repos with extensive history
API rate limits Wait 15 minutes before retrying
Network issues Queue timeout is 15 minutes; retry may succeed
Permissions Verify app has Contents: Read access

Retry: /ecc-tools analyze


Report Issue | ECC Tools

Comment thread scripts/hooks/desktop-notify.js Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 issues found across 1 file (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="scripts/hooks/desktop-notify.js">

<violation number="1" location="scripts/hooks/desktop-notify.js:47">
P2: Notification text is made lossy by stripping backslashes and rewriting quotes, causing inaccurate user-visible summaries.</violation>

<violation number="2" location="scripts/hooks/desktop-notify.js:50">
P2: Internal osascript timeout (3s) is shorter than the hook’s configured 5s timeout, causing avoidable early notification aborts.</violation>

<violation number="3" location="scripts/hooks/desktop-notify.js:50">
P2: Check the `spawnSync` result and emit a warning on `error` or non-zero exit; with `stdio: 'ignore'` this currently fails silently when `osascript` is denied permissions or times out.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment thread scripts/hooks/desktop-notify.js Outdated
Comment thread scripts/hooks/desktop-notify.js
Comment thread scripts/hooks/desktop-notify.js Outdated
@ecc-tools

ecc-tools Bot commented Mar 24, 2026

Copy link
Copy Markdown
Contributor

Analyzing 5000 commits...

@ecc-tools

ecc-tools Bot commented Mar 24, 2026

Copy link
Copy Markdown
Contributor

❌ Analysis Failed

Not Found - https://docs.github.com/rest/git/refs#get-a-reference

Troubleshooting
Cause Resolution
Large repository Analysis may timeout on repos with extensive history
API rate limits Wait 15 minutes before retrying
Network issues Queue timeout is 15 minutes; retry may succeed
Permissions Verify app has Contents: Read access

Retry: /ecc-tools analyze


Report Issue | ECC Tools

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
scripts/hooks/desktop-notify.js (1)

63-75: Consider defensive null check for raw parameter.

Line 67's raw.trim() would throw TypeError if raw is unexpectedly null or undefined. While the catch block handles this gracefully and run-with-flags.js typically provides a string, a defensive check would make the function more robust for direct callers.

🛡️ Optional defensive check
 function run(raw) {
   try {
     if (!isMacOS) return raw;
 
-    const input = raw.trim() ? JSON.parse(raw) : {};
+    const input = raw && raw.trim() ? JSON.parse(raw) : {};
     const summary = extractSummary(input.last_assistant_message);
     notifyMacOS(TITLE, summary);
   } catch (err) {
     log(`[DesktopNotify] Error: ${err.message}`);
   }
 
-  return raw;
+  return raw ?? '';
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/hooks/desktop-notify.js` around lines 63 - 75, The run function calls
raw.trim() which will throw if raw is null/undefined; add a defensive
null/undefined check at the top of run (or normalize raw to an empty string) so
you only call .trim() on a string; update the early-return behavior to still
return raw (or the normalized string) for direct callers, and keep the existing
try/catch and notifyMacOS(TITLE, summary) flow—refer to the run function and
extractSummary/notifyMacOS usage when locating the change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@scripts/hooks/desktop-notify.js`:
- Around line 63-75: The run function calls raw.trim() which will throw if raw
is null/undefined; add a defensive null/undefined check at the top of run (or
normalize raw to an empty string) so you only call .trim() on a string; update
the early-return behavior to still return raw (or the normalized string) for
direct callers, and keep the existing try/catch and notifyMacOS(TITLE, summary)
flow—refer to the run function and extractSummary/notifyMacOS usage when
locating the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e54bd14a-535b-44e3-b1d2-3610ca5cdc9c

📥 Commits

Reviewing files that changed from the base of the PR and between ec5854b and c21118e.

📒 Files selected for processing (2)
  • hooks/hooks.json
  • scripts/hooks/desktop-notify.js
🚧 Files skipped from review as they are similar to previous changes (1)
  • hooks/hooks.json

@affaan-m

Copy link
Copy Markdown
Owner

thanks for the pr. quick triage pass done, a maintainer will take a closer look soon.

Add a new Stop hook that sends a native macOS notification with the
task summary (first line of last_assistant_message) when Claude finishes
responding. Uses osascript via spawnSync for shell injection safety.
Supports run-with-flags fast require() path. Only active on standard
and strict profiles; silently skips on non-macOS platforms.
- Replace JSON.stringify with curly quote substitution for AppleScript
  compatibility (AppleScript does not support \" backslash escapes)
- Reduce spawnSync timeout from 5000ms to 3000ms to leave headroom
  within the 5s hook deadline
- Check spawnSync result and log warning on failure via stderr
- Restore osascript timeout to 5000ms, increase hook deadline to 10s
  for sufficient headroom
@pythonstrup pythonstrup force-pushed the feat/desktop-notify-hook branch from c21118e to f6b1048 Compare March 25, 2026 07:03
@ecc-tools

ecc-tools Bot commented Mar 25, 2026

Copy link
Copy Markdown
Contributor

Analyzing 5000 commits...

@ecc-tools

ecc-tools Bot commented Mar 25, 2026

Copy link
Copy Markdown
Contributor

Analysis Failed

Not Found - https://docs.github.com/rest/git/refs#get-a-reference

Troubleshooting
Cause Resolution
Large repository Analysis may timeout on repos with extensive history
API rate limits Wait 15 minutes before retrying
Network issues Queue timeout is 15 minutes; retry may succeed
Permissions Verify app has Contents: Read access

Retry: /ecc-tools analyze


Report Issue | ECC Tools

@affaan-m

Copy link
Copy Markdown
Owner

ECC Tools had a ref-resolution bug on PR analysis retries and fork/base lookups. The app-side fixes are merged upstream in ECC-Tools#13 and ECC-Tools#15, so this failure path is now fixed in the app code. Once that deployed version is live, rerunning /ecc-tools analyze on this PR should analyze the PR head commit instead of trying to resolve the wrong git ref path.

@affaan-m affaan-m merged commit 678fb6f into affaan-m:main Mar 25, 2026
39 checks passed
FrancescoRosciano pushed a commit to FRosciano-Mambo/everything-claude-code that referenced this pull request Jun 1, 2026
…hook

feat: add macOS desktop notification Stop hook
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants