@seonggukchoi/claude-code-notifier
v0.6.0
Published
Claude Code terminal notification plugin for macOS using terminal-notifier and Telegram
Downloads
320
Maintainers
Readme
@seonggukchoi/claude-code-notifier
Multi-channel notification plugin for Claude Code. Sends notifications via macOS native notifications (terminal-notifier) and/or Telegram Bot API when sessions start, complete, error out, or when subagent/tool executions occur.
Prerequisites
- macOS channel: Requires terminal-notifier:
brew install terminal-notifier- Telegram channel: Requires a Telegram Bot token and chat ID.
Installation
Install the plugin via the Claude Code CLI:
claude plugin add @seonggukchoi/claude-code-notifier@latestClaude Code will install the plugin and register the hooks automatically.
Configuration
Create a configuration file at ~/.claude/plugins/claude-code-notifier/config.json:
{
"locale": "ko",
"workspace": "home-workspace",
"events": {
"toolExecuting": { "enabled": false },
"toolCompleted": { "enabled": false },
"sessionCompleted": { "message": "All done!" },
"decisionNeeded": { "message": "Need input: {{question}}" }
},
"channels": {
"macos": {
"enabled": true
},
"telegram": {
"enabled": true,
"botToken": "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11",
"chatId": "987654321"
}
}
}| Option | Type | Default | Description |
| ----------- | -------- | ----------- | ------------------------------------------------------------------------------------ |
| locale | string | "en" | Notification language ("en", "ko") |
| workspace | string | OS hostname | Device label appended to Telegram notification titles as [workspace] (see below) |
| events | object | — | Global per-event configuration (see below) |
| channels | object | — | Notification channel configuration |
If the config file is missing or contains an invalid locale, the plugin falls back to English.
Migration from v1
If your config file does not have a channels key (v1 format), the plugin will automatically migrate it by adding channels.macos.enabled: true and writing the updated config back to disk.
Channels
macOS (macos)
| Property | Type | Default | Description |
| --------- | --------- | ------- | ----------------------------------------- |
| enabled | boolean | true | Enable/disable macOS native notifications |
| events | object | — | Per-event overrides for this channel |
Telegram (telegram)
| Property | Type | Default | Description |
| ------------------------- | --------- | ------- | ------------------------------------------------------------------- |
| enabled | boolean | false | Enable/disable Telegram notifications |
| botToken | string | — | Required. Telegram Bot API token |
| chatId | string | — | Required. Target chat/group ID |
| connectAttemptTimeoutMs | number | 2000 | Milliseconds allowed per address family when connecting (see below) |
| events | object | — | Per-event overrides for this channel |
Slow networks:
api.telegram.orgresolves to both an IPv4 and an IPv6 address, and Node tries them one after another. Node's own budget for each attempt is 250 ms, which is short enough to abandon a working IPv4 connection on a slow link; if the host has no global IPv6 route, the fallback attempt fails too and no notification is delivered. This plugin raises the budget to 2000 ms by default. IncreaseconnectAttemptTimeoutMsfurther if delivery still fails on a very slow link. Accepted values are integers from 1 to 2147483647; a value outside that range, or one that is not an integer, is ignored and the 2000 ms default applies, while Node raises any accepted value below 10 ms up to 10 ms.
Security note: Your
botTokenis stored in a local config file. Make sure the file has appropriate permissions and is not committed to version control.
Event configuration
Events are configured at two levels:
- Global (
events): Applies to all channels by default. - Per-channel (
channels.<name>.events): Overrides the global setting for that specific channel.
{
"events": {
"toolExecuting": { "enabled": false },
"sessionCompleted": { "enabled": true }
},
"channels": {
"macos": {
"enabled": true
},
"telegram": {
"enabled": true,
"botToken": "...",
"chatId": "...",
"events": {
"toolExecuting": { "enabled": true },
"sessionCompleted": { "enabled": false }
}
}
}
}In this example:
- macOS receives
sessionCompletedbut nottoolExecuting(follows global). - Telegram receives
toolExecutingbut notsessionCompleted(channel override wins).
Each event can be configured with:
| Property | Type | Default | Description |
| --------- | --------- | ------- | --------------------------------- |
| enabled | boolean | true | Whether to send this notification |
| message | string | — | Custom message (overrides i18n) |
Available event keys:
| Key | Description | Template variable |
| --------------------- | ------------------------------- | ----------------- |
| sessionStarted | Session started (busy) | — |
| sessionCompleted | Session completed | — |
| sessionError | An error occurred | — |
| sessionCompacted | Session compacted | — |
| permissionRequested | Permission approval requested | — |
| decisionNeeded | Question tool (decision needed) | {{question}} |
| subagentStarted | Subagent task started | {{description}} |
| subagentCompleted | Subagent task completed | — |
| toolExecuting | MCP tool executing | {{toolName}} |
| toolCompleted | MCP tool completed | {{toolName}} |
Omitted events default to { "enabled": true } with the i18n message.
Hooks
This plugin uses Claude Code's hooks system. The following hooks are registered automatically via plugin.json:
| Hook | Event Key | Description |
| ------------------- | ------------------- | ------------------------------- |
| SessionStart | sessionStarted | Session started |
| Stop | sessionCompleted | Session completed |
| StopFailure | sessionError | An error occurred |
| PostCompact | sessionCompacted | Session compacted |
| PermissionRequest | permissionRequested | Permission approval requested |
| Notification | decisionNeeded | Decision needed from user |
| SubagentStart | subagentStarted | Subagent task started |
| SubagentStop | subagentCompleted | Subagent task completed |
| PreToolUse | toolExecuting | MCP tool executing (mcp__*) |
| PostToolUse | toolCompleted | MCP tool completed (mcp__*) |
Notifications
Session events
| Event Key | Title | Sound | Description |
| ------------------ | ------------------ | ----- | ---------------------- |
| sessionStarted | ⚡ Claude Code | Pop | Session started (busy) |
| sessionCompleted | ✅ Claude Code | Hero | Session completed |
| sessionError | ❌ Claude Code | Basso | An error occurred |
| sessionCompacted | 📦 Claude Code | Purr | Session compacted |
Permission events
| Event Key | Title | Sound | Description |
| -------------------- | ------------------ | ----- | ------------------------- |
| permissionRequested | 🔐 Claude Code | Glass | Permission approval asked |
Decision & subagent events
| Event Key | Title | Sound | Description |
| ------------------ | ------------------ | --------- | ------------------------------- |
| decisionNeeded | 🙋 Claude Code | Glass | Question tool (decision needed) |
| subagentStarted | 🤖 Claude Code | Submarine | Subagent task started |
| subagentCompleted | 🤖 Claude Code | Hero | Subagent task completed |
Tool events
| Event Key | Title | Sound | Description |
| --------------- | ------------------ | ----- | ------------------ |
| toolExecuting | 🔧 Claude Code | Tink | MCP tool executing |
| toolCompleted | ✓ Claude Code | Blow | MCP tool completed |
Notification context
Every notification message is prefixed with a context label so you can tell sessions apart at a glance:
- Session name — if the session has a custom name (e.g. set via
/rename), that name is used. - Working directory — otherwise, the current directory name is used as a fallback.
- Delegated agent — for subagent events (
subagentStarted/subagentCompleted), the agent type is appended to the parent session's context assession(agent-type)— for example,my-project(Explore). Agents delegated through the Agent/Task tool or a workflow run under the parent session, so this makes it clear which session, and which delegated agent, the alert came from instead of showing a bare hash.
The context is resolved per hook event, so renaming a session is reflected on subsequent notifications without restarting it.
Workspace label (Telegram)
Telegram notifications are delivered remotely, so it is not always obvious which machine a notification came from. To make this clear, a workspace label is appended to the Telegram notification title — for example, ⚡ Claude Code [home-workspace].
- Set
workspaceinconfig.jsonto use a custom label. - When
workspaceis omitted, the OS hostname is used as a fallback.
The label is applied to Telegram only. macOS notifications are shown locally, so their titles are left unchanged.
Telegram setup
- Create a bot via @BotFather and copy the bot token.
- Get your chat ID:
- Send a message to your bot.
- Visit
https://api.telegram.org/bot<YOUR_TOKEN>/getUpdatesand findchat.idin the response. - For group chats, the chat ID is typically a negative number.
- Add the configuration to your
config.json:
{
"channels": {
"telegram": {
"enabled": true,
"botToken": "<YOUR_BOT_TOKEN>",
"chatId": "<YOUR_CHAT_ID>"
}
}
}Terminal detection
The plugin detects the current terminal app and includes its icon in notifications. Supported terminals:
- iTerm2
- Cursor
- VS Code
- Zed
- Terminal.app
- Warp
- Hyper
Icons are converted from .icns to .png and cached at ~/.claude/plugins/claude-code-notifier/icons/.
License
MIT
