[FEATURE] Mods: let a plugin send a native notification ($.ui.notify)
Request
Add a way for a mod to send a notification through Claude Code's own notification path, for example $.ui.notify({ title, body }). It would follow the user's preferredNotifChannel the same way Claude Code's own "needs input" and "task complete" notifications do.
Why
A mod that runs long, unattended work needs to tell the user when something happens: "PR #123 is ready for you to merge", "the run stopped at CI". In 2.1.288, the mods API has $.ui.toast, which only shows inside the TUI, and the Notification settings hook event, which observes Claude Code's notifications but can't send one. Neither reaches the user once they've switched away from the terminal.
Claude Code's native notifications go out as a terminal escape (OSC 9/777, or a bell), so the terminal posts them as itself (Ghostty, iTerm2, cmux, Kitty), and clicking one focuses the right window. A mod can't write to that terminal. Its only way out is $.process.run, which starts a child process with no access to the TUI's output stream. So mods fall back to one of these:
osascript -e 'display notification …'on macOS. It shows as coming from Script Editor, and clicking it focuses nothing.- A notifier for one specific terminal, such as
cmux notify --surface "$CMUX_SURFACE_ID", oralerterpluszellij action focus-pane-id. Each mod has to rebuild this for every terminal and multiplexer.
Proposed shape
await $.ui.notify({ title: "Autopilot", body: "PR #123 is ready for you to merge" })
- It goes through the same channel logic as Claude Code's native notifications (
preferredNotifChannel, terminal escape vs. OS notification), so a click focuses the session's own terminal or pane. - It respects the user's notification settings.
claude plugin validatelists the call like any other$call.
Related
- #91870 (mods umbrella). One comment lists "emit a notification" among the
$capabilities an observer plugin needs.
Environment
- Claude Code 2.1.288, mods API (
hooks/register.tsxmodule) - macOS, tested in cmux, and in Ghostty with Zellij