OpenCode plugin for desktop notifications and sounds on permission, completion, and error events.
OpenCode plugin for desktop notifications and sounds on permission, completion, and error events.
OpenCode plugin that plays sounds and sends system notifications when permission is needed, generation completes, errors occur, or the question tool is invoked. Works on macOS, Linux, and Windows.
Install the plugin via the CLI: opencode plug -g @mohak34/opencode-notifier.
Or add manually to your opencode.json:
{
"plugin": ["@mohak34/opencode-notifier@latest"]
}Restart OpenCode. Done.
You'll get notified when:
There's also subagent_complete for when subagents finish, and user_cancelled for when you press ESC to abort -- both are silent by default so you don't get spammed.
macOS: Nothing to do, works out of the box. Shows the Script Editor icon.
Linux: Should work if you already have a notification system setup. If not install libnotify:
sudo apt install libnotify-bin # Ubuntu/Debian
sudo dnf install libnotify # Fedora
sudo pacman -S libnotify # ArchFor sounds, you need one of: paplay, aplay, mpv, or ffplay
Windows: Works out of the box. But heads up:
.wav files work (not mp3)C:/Users/You/sounds/alert.wav not ~/WSL: It's recommeneded to set customIconPath pointing to a file on Windows filesystem
due to issues with path translation (can be copied from logos folder from this repository).
This path will be passed down to snoretoast-*.exe
In opencode-notifier.json config:
"showIcon": true,
"customIconPath": "C:\\Users\\jhon\\Documents\\opencode-logo-dark.png",Create ~/.config/opencode/opencode-notifier.json with the defaults:
…{
"sound": true,
"notification": true,
"bell": false,
"timeout": 5,
"showProjectName": true,
"showFullPath": false,
"showSessionTitle": false,
"showIcon": true,
"suppressWhenFocused": true,
"enableOnDesktop": false,
"notificationSystem": "osascript",
"suppressGhosttySound": false
}sound - Turn sounds on/off (default: true)notification - Turn notifications on/off (default: true)bell - Emit terminal BEL (\x07) on events (default: false). Behavior depends on your terminal/WM settingstimeout - How long notifications show in seconds, Linux only (default: 5)showProjectName - Show folder name in notification title (default: true)showFullPath - Show full absolute path instead of folder name in notification title and {projectName} token (default: false). When true, shows OpenCode (/home/user/projects/myapp) instead of OpenCode (myapp)showSessionTitle - Include the session title in notification messages via {sessionTitle} placeholder (default: false)showIcon - Show OpenCode icon, Windows/Linux only (default: true)customIconPath - Path to a custom icon for notifications. Useful on WSL where Windows paths are needed (default: null)suppressWhenFocused - Skip notifications and sounds when the terminal is the active window (default: true). See Focus detection for platform detailsenableOnDesktop - Run the plugin on Desktop and Web clients (default: false). When false, the plugin only runs on CLI. Set to true if you want notifications/sounds/commands on Desktop/Web — useful if you want custom commands (Telegram, webhooks) but don't care about built-in notificationsnotificationSystem - macOS only: "osascript", "node-notifier", or "ghostty" (default: "osascript"). Use "ghostty" if you're running Ghostty terminal for native OSC 9 notificationssuppressGhosttySound - macOS only: when true with notificationSystem: "ghostty", skips the plugin's sound to avoid duplicating macOS Notification Center's default sound (default: false)minDuration - Suppress complete and subagent_complete notifications when session finishes faster than this many seconds (default: 0). See Minimum duration thresholdlinux.grouping - Linux only: replace notifications in-place instead of stacking (default: false). Requires notify-send 0.8+Control each event separately:
…user_cancelled fires when you press ESC to abort a session. It's silent by default so intentional cancellations don't trigger error alerts. Set sound or notification to true if you want confirmation when cancelling.
session_started fires when a new top-level session is created. user_message fires when a user message is submitted in a top-level session. client_connected fires shortly after the plugin initializes and is best-effort (there is no dedicated SDK connection event from plugin context).
The command property controls whether the custom command (see Custom commands) runs for that event. Defaults to true for all events. Set it to false to suppress the command for specific events without disabling it globally.
bell is terminal-driven and may be audible, visual, both, or ignored depending on your terminal setup. Quick check: printf '\a'.
Or use true/false for both:
{
"events": {
"complete": false
}
}Customize the notification text:
…Messages support placeholder tokens that get replaced with actual values:
{sessionTitle} - The title/summary of the current session (e.g. "Fix login bug"){agentName} - Subagent name extracted from session titles with (@name subagent) suffix (e.g. builder, codebase-researcher), empty for non-subagent sessions{projectName} - The project folder name{timestamp} - Current time in HH:MM:SS format (e.g. "14:30:05"){turn} - Global notification counter that persists across restarts (e.g. 1, 2, 3). Stored in ~/.config/opencode/opencode-notifier-state.jsonWhen showSessionTitle is false, {sessionTitle} is replaced with an empty string. Any trailing separators (: , -, |) are automatically cleaned up when a placeholder resolves to empty.
To disable session titles in messages without changing showSessionTitle, just remove the {sessionTitle} placeholder from your custom messages.
The {timestamp} and {turn} placeholders also work in custom command args.
Use your own sound files:
{
"sounds": {
"permission": "/path/to/alert.wav",
"complete": "/path/to/done.wav",
"subagent_complete": "/path/to/subagent-done.wav",
"error": "/path/to/error.wav",
"question": "/path/to/question.wav",
"user_cancelled": "/path/to/cancelled.wav",
"plan_exit": "/path/to/plan-ready.wav",
"session_started": "/path/to/session-started.wav",
"user_message": "/path/to/user-message.wav",
"client_connected": "/path/to/client-connected.wav"
}
}Platform notes:
Set per-event volume from 0 to 1:
{
"volumes": {
"permission": 0.6,
"complete": 0.3,
"subagent_complete": 0.15,
"error": 1,
"question": 0.7,
"user_cancelled": 0.5,
"plan_exit": 0.6,
"session_started": 0.35,
"user_message": 0.2,
"client_connected": 0.45
}
}0 = mute, 1 = full volume0..1 are clamped automaticallyRun your own script when something happens. Use {event}, {message}, {sessionTitle}, {agentName}, {projectName}, {timestamp}, and {turn} as placeholders:
{
"command": {
"enabled": true,
"path": "/path/to/your/script",
"args": ["{event}", "{message}"],
"minDuration": 10
}
}enabled - Turn command on/offpath - Path to your script/executableargs - Arguments to pass, can use {event}, {message}, {sessionTitle}, {agentName}, {projectName}, {timestamp}, and {turn} tokensminDuration - Skip if response was quick, avoids spam (seconds)Token values are passed as argv values and are not shell-escaped for use inside
script source. Do not put {message}, {sessionTitle}, or other dynamic tokens
inside a sh -c, bash -c, powershell -Command, or similar script string.
Use a wrapper script and pass the tokens as separate arguments instead.
Custom commands run with the same user permissions as OpenCode, so only enable
scripts you trust.
{
"command": {
"enabled": true,
"path": "/bin/bash",
"args": [
"-c",
"printf '[%s] %s\\n' \"$1\" \"$2\" >> /tmp/opencode.log",
"opencode-notifier",
"{event}",
"{message}"
]
}
}osascript (default): Reliable but shows Script Editor icon
{
"notificationSystem": "osascript"
}node-notifier: Shows OpenCode icon but might miss notifications sometimes
{
"notificationSystem": "node-notifier"
}NOTE: If you go with node-notifier and start missing notifications, just switch back or remove the option from the config. Users have reported issues with using node-notifier for receiving only sounds and no notification popups.
If you're using Ghostty terminal, you can use its native notification system via OSC 9 escape sequences:
{
"notificationSystem": "ghostty"
}This sends notifications directly through the terminal instead of using system notification tools. Works on any platform where Ghostty is running.
macOS: Ghostty delivers notifications through macOS Notification Center, which plays its own default sound. This can result in duplicate audio with the plugin's sound effects. Set suppressGhosttySound to true to skip the plugin's sound:
{
"notificationSystem": "ghostty",
"suppressGhosttySound": true
}Note: custom sounds configured via the sounds section still play — only default (bundled) sounds are suppressed.
If you're using Ghostty inside tmux, enable passthrough in your tmux config so OSC 9 notifications can pass through:
set -g allow-passthrough onThen reload tmux config:
tmux source-file ~/.tmux.confWhen suppressWhenFocused is true (the default), notifications and sounds are skipped if the terminal running OpenCode is the active/focused window. The idea is simple: if you're already looking at it, you don't need an alert.
To disable this and always get notified:
{
"suppressWhenFocused": false
}You can suppress complete and subagent_complete notifications for short-lived sessions. Set minDuration to the number of seconds a session must exceed to trigger a done notification:
{
"minDuration": 10
}With the above, if OpenCode finishes in under 10 seconds, no notification, sound, bell, or command is fired. Default is 0 (no threshold).
This is independent of command.minDuration, which only controls whether the custom command runs.
| Platform | Method | Requirements | Status |
|---|---|---|---|
| macOS | AppleScript (System Events) |
None | Untested |
| Linux X11 | `xdo |
No open issues yet, or sync has not completed.