Desktop + mobile notifications for Claude Code.
Get notified when Claude finishes a task, errors out, or waits for your input — even if you've switched to another window or walked away.
| Event | Toast | Feishu |
|---|---|---|
Session ends (Stop) |
Session summary | Green card |
Session errors (StopFailure) |
Error details | Red card |
Permission prompt (Notification) |
Tool name | Orange card |
Idle / waiting (Notification) |
Prompt text | Yellow card |
Claude Code Hooks (Stop / StopFailure / Notification)
├── PowerShell toast-notify.ps1 → Windows Toast
└── Node.js notify.mjs → Feishu Webhook → Phone
Two independent channels. If one fails, the other still works.
git clone https://github.com/alexyan0431/ClaudeTether.git
cd ClaudeTether
cp config.example.json config.jsonEdit config.json — fill in your Feishu webhook URL and optional secret:
{
"feishu": {
"webhook_url": "https://open.feishu.cn/open-apis/bot/v2/hook/your-hook-id",
"secret": ""
},
"notification_types": {
"permission_prompt": true,
"idle_prompt": true,
"auth_success": false
}
}Add the following to your project's .claude/settings.local.json:
{
"hooks": {
"Stop": [
{
"hooks": [
{ "type": "command", "command": "/path/to/ClaudeTether/src/toast-notify.ps1", "shell": "powershell" },
{ "type": "command", "command": "node /path/to/ClaudeTether/src/notify.mjs", "shell": "powershell" }
]
}
],
"StopFailure": [
{
"hooks": [
{ "type": "command", "command": "/path/to/ClaudeTether/src/toast-notify.ps1", "shell": "powershell" },
{ "type": "command", "command": "node /path/to/ClaudeTether/src/notify.mjs", "shell": "powershell" }
]
}
],
"Notification": [
{
"hooks": [
{ "type": "command", "command": "/path/to/ClaudeTether/src/toast-notify.ps1", "shell": "powershell", "async": true },
{ "type": "command", "command": "node /path/to/ClaudeTether/src/notify.mjs", "shell": "powershell", "async": true }
]
}
]
}
}Replace /path/to/ClaudeTether with your actual path (e.g. D:\\hobbies\\ClaudeTether).
Notes:
Stop/StopFailurehooks are synchronous (noasync) so notifications complete before Claude Code exits.Notificationhooks are async so they don't block Claude Code during operation."shell": "powershell"is required on Windows for reliable stdin piping.
# Test Toast
echo '{"hook_event_name":"Stop","session_id":"test","cwd":"C:\\dev","last_assistant_message":"It works!","stop_hook_active":false}' `
| powershell -ExecutionPolicy Bypass -File src\toast-notify.ps1
# Test Feishu
echo '{"hook_event_name":"Stop","session_id":"test","cwd":"C:\\dev","last_assistant_message":"It works!","stop_hook_active":false}' `
| node src\notify.mjs- Windows 10/11 (Toast uses WinRT API)
- PowerShell 5.1 (built-in)
- Node.js 18+ (Feishu channel only)
src/
toast-notify.ps1 Hook-driven Toast (reads stdin JSON)
toast.ps1 Standalone Toast (accepts -Title/-Body)
notify.mjs Feishu webhook entry point
format-card.mjs Feishu interactive card builder
send-webhook.mjs HTTP POST with HMAC-SHA256 signing
config.mjs Config loader (file + env var overrides)
config.example.json Config template
config.json Your config (gitignored)
Set env vars to override config.json without editing the file:
CLAUDETETHER_FEISHU_WEBHOOK_URLCLAUDETETHER_FEISHU_SECRET