import type { Metadata } from "next"; import { CodeBlock } from "../../components/code-block"; import { Callout } from "../../components/callout"; export const metadata: Metadata = { title: "Notifications", description: "Send desktop notifications from AI agents and scripts in cmux. CLI, OSC 99/777 escape sequences, and Claude Code hooks integration.", }; export default function NotificationsPage() { return ( <>

Notifications

cmux supports desktop notifications, allowing AI agents and scripts to alert you when they need attention.

Lifecycle

  1. Received — notification appears in panel, desktop alert fires (if not suppressed)
  2. Unread — badge shown on workspace tab
  3. Read — cleared when you view that workspace
  4. Cleared — removed from panel

Suppression

Desktop alerts are suppressed when:

Notification panel

Press ⌘⇧I to open the notification panel. Click a notification to jump to that workspace. Press ⌘⇧U to jump directly to the workspace with the most recent unread notification.

Sending notifications

CLI

{`cmux notify --title "Task Complete" --body "Your build finished" cmux notify --title "Claude Code" --subtitle "Waiting" --body "Agent needs input"`}

OSC 777 (simple)

The RXVT protocol uses a fixed format with title and body:

{`printf '\\e]777;notify;My Title;Message body here\\a'`} {`notify_osc777() { local title="$1" local body="$2" printf '\\e]777;notify;%s;%s\\a' "$title" "$body" } notify_osc777 "Build Complete" "All tests passed"`}

OSC 99 (rich)

The Kitty protocol supports subtitles and notification IDs:

{`# Format: ESC ] 99 ; ; ESC \\ # Simple notification printf '\\e]99;i=1;e=1;d=0:Hello World\\e\\\\' # With title, subtitle, and body printf '\\e]99;i=1;e=1;d=0;p=title:Build Complete\\e\\\\' printf '\\e]99;i=1;e=1;d=0;p=subtitle:Project X\\e\\\\' printf '\\e]99;i=1;e=1;d=1;p=body:All tests passed\\e\\\\'`}
Feature OSC 99 OSC 777
Title + body Yes Yes
Subtitle Yes No
Notification ID Yes No
Complexity Higher Lower
Use OSC 777 for simple notifications. Use OSC 99 when you need subtitles or notification IDs. Use the CLI (cmux notify) for the easiest integration.

Claude Code hooks

cmux integrates with{" "} Claude Code{" "} via hooks to notify you when tasks complete.

1. Create the hook script

{`#!/bin/bash # Skip if not in cmux [ -S /tmp/cmux.sock ] || exit 0 EVENT=$(cat) EVENT_TYPE=$(echo "$EVENT" | jq -r '.event // "unknown"') TOOL=$(echo "$EVENT" | jq -r '.tool_name // ""') case "$EVENT_TYPE" in "Stop") cmux notify --title "Claude Code" --body "Session complete" ;; "PostToolUse") [ "$TOOL" = "Task" ] && cmux notify --title "Claude Code" --body "Agent finished" ;; esac`} {`chmod +x ~/.claude/hooks/cmux-notify.sh`}

2. Configure Claude Code

{`{ "hooks": { "Stop": ["~/.claude/hooks/cmux-notify.sh"], "PostToolUse": [ { "matcher": "Task", "hooks": ["~/.claude/hooks/cmux-notify.sh"] } ] } }`}

Restart Claude Code to apply the hooks.

Integration examples

Notify after long command

{`# Add to your shell config notify-after() { "$@" local exit_code=$? if [ $exit_code -eq 0 ]; then cmux notify --title "✓ Command Complete" --body "$1" else cmux notify --title "✗ Command Failed" --body "$1 (exit $exit_code)" fi return $exit_code } # Usage: notify-after npm run build`}

Python

{`import sys def notify(title: str, body: str): """Send OSC 777 notification.""" sys.stdout.write(f'\\x1b]777;notify;{title};{body}\\x07') sys.stdout.flush() notify("Script Complete", "Processing finished")`}

Node.js

{`function notify(title, body) { process.stdout.write(\`\\x1b]777;notify;\${title};\${body}\\x07\`); } notify('Build Done', 'webpack finished');`}

tmux passthrough

If using tmux inside cmux, enable passthrough:

{`set -g allow-passthrough on`} {`printf '\\ePtmux;\\e\\e]777;notify;Title;Body\\a\\e\\\\'`} ); }