VibeNotification
English | 中文
Stop waiting when vibe coding — Give a notification when Claude Code or Codex finishes replies —
Installation
- Stable (PyPI):
pip install vibe-notification - Dev:
pip install -e . - Optional venv:
python -m venv venv && source venv/bin/activate - Verify:
python -m vibe_notification --test(should toast and chime when enabled) - Interactive setup:
python -m vibe_notification --config- Default config file:
~/.config/vibe-notification/config.json - Make sure both sound and system notifications are enabled
- Default config file:
Quick Start
Claude Code
- Recommended hook:
Stop(when each main reply completes). - If what you want is "notify me when this reply is done", use
Stop. That is the default and the only recommended hook. - Do not attach the notifier command to
SessionEndorSubagentStop: VibeNotification ignores them by default to avoid duplicate alerts from session-exit, subagent, or tool-chain lifecycle events. - On macOS, VibeNotification now defaults to
senderoff in Claude Code hook contexts and terminal-hosted CLI contexts for more reliable banners. If you explicitly want host-app attribution/icon, setVIBE_NOTIFICATION_SENDER_MODE=force. - If a notification appears only in Notification Center, check
System Settings > Notificationsfor the effective app (terminal-notifierwhen sender is off, or the host app such as VS Code / Terminal when sender is auto/force). Make sure notifications are allowed, banner/alert style is enabled, and Focus is not suppressing them. - Edit
~/.claude/settings.jsonand add a Stop hook:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "env VIBE_NOTIFICATION_SENDER_MODE=off python -m vibe_notification"
}
]
}
]
}
}
- Example full settings snippet with environment variables:
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"env": {
"ANTHROPIC_AUTH_TOKEN": "xxx",
"ANTHROPIC_BASE_URL": "https://open.bigmodel.cn/api/anthropic",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "glm-4.6",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "glm-4.6",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "glm-4.6",
"ANTHROPIC_MODEL": "glm-4.6",
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1",
"DISABLE_ERROR_REPORTING": "1",
"DISABLE_TELEMETRY": "1",
"MCP_TIMEOUT": "60000"
},
"hooks": {
"Stop": [
{
"hooks": [
{
"command": "env VIBE_NOTIFICATION_SENDER_MODE=off python -m vibe_notification",
"type": "command"
}
]
}
]
},
"includeCoAuthoredBy": false,
"outputStyle": "engineer-professional"
}
Codex CLI
Use a Stop hook in ~/.codex/config.toml. It runs when the main agent stops the current turn after its tool calls and multi-step work; receiving a user message, a subagent stopping, or the whole session exiting does not count as task completion:
[[hooks.Stop]]
[[hooks.Stop.hooks]]
type = "command"
command = "python3 -m vibe_notification"
timeout = 30
Do not configure this Stop hook and the legacy notify command at the same time, because the same task can arrive through both event channels. Legacy agent-turn-complete payloads do not include the commentary / final_answer phase, so no fixed quiet period can reliably identify the final reply. VibeNotification therefore suppresses legacy notify events by default. Only when an older Codex version cannot use hooks, set VIBE_ALLOW_LEGACY_CODEX_NOTIFY=1 to restore the 10-second trailing-edge compatibility debounce; long tool calls can still make that mode misclassify an intermediate turn as complete. Use VIBE_DEBOUNCE_COOLDOWN to tune the compatibility delay.
Note: Stop means that the main agent stopped the current turn; it is not a whole-session exit event.
If you only want one notification after the whole Codex session exits, do not use Stop. Use the built-in wrapper:
python -m vibe_notification --wrap-codex
You can pass normal Codex arguments through unchanged:
python -m vibe_notification --wrap-codex -- --help
python -m vibe_notification --wrap-codex -- -C /path/to/project
If you want this as your everyday entrypoint, add a shell alias such as:
alias codexn='python3 -m vibe_notification --wrap-codex --'
Then launch codexn; VibeNotification will fire only once, after the Codex process actually exits.
To inspect your local integration and spot config/semantic mismatches quickly:
python -m vibe_notification --doctor
Typical placement in config.toml:
model_provider = "xxx"
model = "gpt-5.1-codex-max"
model_reasoning_effort = "medium"
disable_response_storage = true
[[hooks.Stop]]
[[hooks.Stop.hooks]]
type = "command"
command = "python3 -m vibe_notification"
timeout = 30
[model_providers.xxx]
name = "xxx"
base_url = "https://xxx/v1"
wire_api = "responses"
requires_openai_auth = true
[tui]
notifications = true
Configuration Recipes
Visual only (no sound)
- Codex
~/.codex/config.toml:
command = "python3 -m vibe_notification --sound 0"
- Claude Code
~/.claude/settings.json:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "python -m vibe_notification --sound 0"
}
]
}
]
}
}
- Quick test:
python -m vibe_notification --sound 0 --test
Sound only (no system toast)
- Codex:
command = "python3 -m vibe_notification --notification 0"
- Claude Code:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "python -m vibe_notification --notification 0"
}
]
}
]
}
}
- Quick test:
python -m vibe_notification --notification 0 --test
Temporary toggles (environment variables)
In a hook command, env NAME=value command sets an environment variable only for the command that follows it. For example:
command = "env VIBE_NOTIFICATION_SENDER_MODE=off python3 -m vibe_notification"
This does not permanently modify your shell or system environment. You can place multiple NAME=value assignments after env; the final arguments are the Python command to execute.
Common environment variables:
| Variable | Accepted values | Effect | Important notes |
|---|---|---|---|
VIBE_NOTIFICATION_SOUND |
0 |
Temporarily disable sound | Only 0 is recognized here; use CLI --sound 1 to force sound on |
VIBE_NOTIFICATION_NOTIFY |
0 |
Temporarily disable system toasts | Does not disable sound; use CLI --notification 1 to force toasts on |
VIBE_NOTIFICATION_SOUND_VOLUME |
0.0–1.0 |
Override sound volume | Values are clamped to the range; the config default is 0.1 |
VIBE_NOTIFICATION_SOUND_TYPE |
e.g. Glass, Ping, Pop, Tink, Basso |
Override the alert sound | Available sounds depend on the operating system |
VIBE_NOTIFICATION_LOG_LEVEL |
DEBUG, INFO, WARNING, ERROR |
Override log level | DEBUG records more detail and can include raw Codex payloads |
VIBE_NOTIFICATION_LANGUAGE |
zh, en |
Override UI language | Invalid values are ignored |
VIBE_NOTIFICATION_SENDER_MODE |
auto, off, force |
Control macOS sender binding | Changes notification attribution, not whether a toast is sent |
VIBE_NOTIFICATION_SENDER_BUNDLE_ID |
a macOS Bundle ID | Select a sender explicitly | Advanced; only effective when sender mode is not off |
VIBE_ALLOW_LEGACY_CODEX_NOTIFY |
1, true, yes, on |
Enable legacy Codex notify compatibility |
Off by default; can misclassify an intermediate turn, so it is not recommended |
VIBE_DEBOUNCE_COOLDOWN |
a non-negative integer in seconds | Set the legacy notify quiet period |
Relevant only when the previous option is enabled; defaults to 10 seconds |
For sound, toast, and log-level settings, precedence is: CLI option > environment variable > config.json > built-in default. Volume, sound type, language, and sender mode have no CLI equivalent, so their precedence is: environment variable > config.json > built-in default.
macOS sender modes
off: Do not pass a host application Bundle ID toterminal-notifier. The notification is still sent and is normally attributed toterminal-notifier. This is recommended for Codex, Claude Code, and terminal hooks because it avoids inheriting VS Code or Terminal notification policies.auto: Detect the context. Normal GUI-hosted runs try to bind the detected host application, while Claude Code hooks and terminal-hosted runs automatically behave likeoff.force: Try to detect and bind the host application even from a terminal context. Use this only when you specifically want notifications attributed to VS Code, Terminal, or another host app.
VIBE_NOTIFICATION_SENDER_MODE=off is not the same as VIBE_NOTIFICATION_NOTIFY=0: the former disables sender binding only; the latter disables system toasts.
Codex examples (replace command under [[hooks.Stop.hooks]] above):
# Temporarily mute sound
command = "env VIBE_NOTIFICATION_SOUND=0 python3 -m vibe_notification"
# Disable all notifications (for debugging)
command = "env VIBE_NOTIFICATION_NOTIFY=0 VIBE_NOTIFICATION_SOUND=0 python3 -m vibe_notification"
# Enable debug logging
command = "env VIBE_NOTIFICATION_LOG_LEVEL=DEBUG python3 -m vibe_notification"
Claude Code example:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "env VIBE_NOTIFICATION_SOUND=0 VIBE_NOTIFICATION_SENDER_MODE=off python -m vibe_notification"
}
]
}
]
}
}
CLI tests:
VIBE_NOTIFICATION_SOUND=0 python -m vibe_notification --test
VIBE_NOTIFICATION_SOUND=0 VIBE_NOTIFICATION_NOTIFY=0 python -m vibe_notification --test
VIBE_NOTIFICATION_LOG_LEVEL=DEBUG python -m vibe_notification --test
VIBE_NOTIFICATION_SENDER_MODE=off python -m vibe_notification --test
Sound type
Available macOS sound types: Glass (default), Ping, Pop, Tink, Basso.
command = "env VIBE_NOTIFICATION_SOUND_TYPE=Ping python3 -m vibe_notification"
# Low tone
command = "env VIBE_NOTIFICATION_SOUND_TYPE=Basso python3 -m vibe_notification"
Claude Code:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "env VIBE_NOTIFICATION_SOUND_TYPE=Pop python -m vibe_notification"
}
]
}
]
}
}
Test different sounds:
VIBE_NOTIFICATION_SOUND_TYPE=Tink python -m vibe_notification --test
VIBE_NOTIFICATION_SOUND_TYPE=Ping python -m vibe_notification --test
Volume control
Volume range is 0.0–1.0.
command = "env VIBE_NOTIFICATION_SOUND_VOLUME=0.2 python3 -m vibe_notification"
command = "env VIBE_NOTIFICATION_SOUND_VOLUME=0.5 python3 -m vibe_notification"
command = "env VIBE_NOTIFICATION_SOUND_VOLUME=0 python3 -m vibe_notification" # mute
Claude Code:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "env VIBE_NOTIFICATION_SOUND_VOLUME=0.3 python -m vibe_notification"
}
]
}
]
}
}
Quick test:
VIBE_NOTIFICATION_SOUND_VOLUME=0.1 python -m vibe_notification --test
VIBE_NOTIFICATION_SOUND_VOLUME=0.8 python -m vibe_notification --test
Notification timeout (current platform limitation)
Edit ~/.config/vibe-notification/config.json:
{
"enable_sound": true,
"enable_notification": true,
"notification_timeout": 10000,
"sound_type": "Glass",
"sound_volume": 0.1,
"log_level": "INFO"
}
notification_timeout is currently a reserved configuration field. The platform adapters do not yet pass it to the macOS, Linux, or Windows notification backends, so the value cannot guarantee dismissal after an exact number of milliseconds. Actual display duration is primarily controlled by operating-system notification style, notification-center settings, and focus mode. Keep the default 10000 for now, and do not rely on this field for timing-critical alerts.
Or use the interactive config:
python -m vibe_notification --config
Prebuilt combos
Focus mode (low volume + gentle tone, with toast retained):
command = "env VIBE_NOTIFICATION_SOUND_VOLUME=0.1 VIBE_NOTIFICATION_SOUND_TYPE=Basso python3 -m vibe_notification"
Meeting mode (sound only, louder, specific tone):
command = "env VIBE_NOTIFICATION_NOTIFY=0 VIBE_NOTIFICATION_SOUND_VOLUME=0.7 VIBE_NOTIFICATION_SOUND_TYPE=Ping python3 -m vibe_notification"
Debug mode (keep current sound/toast toggles + debug logs):
command = "env VIBE_NOTIFICATION_LOG_LEVEL=DEBUG python3 -m vibe_notification"
CLI Reference
Command-line options
| Option | Type | Default | Description |
|---|---|---|---|
event_json |
positional | - | Optional Codex event JSON string |
--test |
flag | - | Send a test notification |
--config |
flag | - | Interactive configuration |
--sound {0,1} |
choice | config value | Enable/disable sound (0=off, 1=on) |
--notification {0,1} |
choice | config value | Enable/disable system notification (0=off, 1=on) |
--log-level {DEBUG,INFO,WARNING,ERROR} |
choice | config value | Set log level |
--doctor |
flag | - | Check local Claude Code, Codex, VibeNotification, and notification-backend integration |
--wrap-codex |
flag | - | Launch Codex and notify once when its process exits; put Codex arguments after -- |
--version |
flag | - | Show version |
Config file
Location: ~/.config/vibe-notification/config.json
| Key | Type | Default | Description |
|---|---|---|---|
enable_sound |
bool | true |
Enable sound |
enable_notification |
bool | true |
Enable system notification |
notification_timeout |
int | 10000 |
Reserved; not currently applied by platform adapters |
sound_type |
string | "Glass" |
Sound type |
sound_volume |
float | 0.1 |
Sound volume |
log_level |
string | "INFO" |
Log level |
detect_conversation_end |
bool | true |
Detect end of conversation |
language |
string | "zh" |
UI language: zh or en |
macos_sender_mode |
string | "auto" |
Sender mode for macOS: auto, off, or force |
Additional notes:
"default"in olderconfig.example.jsonfiles and existing configs remains supported for compatibility.- Keep
detect_conversation_endset totrue. When disabled, ordinary non-terminal events may follow legacy notification behavior, although explicitly suppressed safety events remain silent. notification_timeoutis not currently applied by platform notification adapters; see “Notification timeout (current platform limitation)” above.
Environment variables
| Env | Description | Example |
|---|---|---|
VIBE_NOTIFICATION_SOUND |
Override sound setting | VIBE_NOTIFICATION_SOUND=0 |
VIBE_NOTIFICATION_NOTIFY |
Override notification setting | VIBE_NOTIFICATION_NOTIFY=0 |
VIBE_NOTIFICATION_LOG_LEVEL |
Override log level | VIBE_NOTIFICATION_LOG_LEVEL=DEBUG |
VIBE_NOTIFICATION_SENDER_MODE |
Override macOS sender binding mode | VIBE_NOTIFICATION_SENDER_MODE=off |
VIBE_NOTIFICATION_SOUND_VOLUME |
Override and clamp volume to 0.0–1.0 |
VIBE_NOTIFICATION_SOUND_VOLUME=0.3 |
VIBE_NOTIFICATION_SOUND_TYPE |
Override the system alert sound | VIBE_NOTIFICATION_SOUND_TYPE=Glass |
VIBE_NOTIFICATION_LANGUAGE |
Override UI language | VIBE_NOTIFICATION_LANGUAGE=en |
VIBE_NOTIFICATION_SENDER_BUNDLE_ID |
Set the macOS sender Bundle ID | VIBE_NOTIFICATION_SENDER_BUNDLE_ID=com.apple.Terminal |
VIBE_ALLOW_LEGACY_CODEX_NOTIFY |
Explicitly enable unreliable legacy Codex notify debounce | VIBE_ALLOW_LEGACY_CODEX_NOTIFY=1 |
VIBE_DEBOUNCE_COOLDOWN |
Legacy Codex notify quiet period in seconds | VIBE_DEBOUNCE_COOLDOWN=30 |
Typical commands
# Test (toast + sound)
python -m vibe_notification --test
# Toast only
python -m vibe_notification --sound 0 --test
# Sound only
python -m vibe_notification --notification 0 --test
# Debug logs
python -m vibe_notification --log-level DEBUG --test
Integration checks
Check whether Codex and Claude Code use the recommended Stop hook, and inspect sound, toast, and macOS backend status:
python -m vibe_notification --doctor
Test whether the current settings can produce a toast and sound:
python -m vibe_notification --test
Legacy Codex agent-turn-complete JSON is only for compatibility when hooks are unavailable and is suppressed by default. Do not treat manually passing this JSON as the recommended Codex configuration or normal test path.
Publishing to PyPI
- Bump the version in
pyproject.toml(single source of truth). - Install tooling:
python -m pip install --upgrade build twine. - Build:
python -m build(creates.tar.gzand.whlunderdist/). - Validate:
python -m twine check dist/*. - Upload:
TWINE_USERNAME=__token__ TWINE_PASSWORD=<pypi-token> python -m twine upload dist/*(use--repository testpypito dry run). - Install + verify:
pip install -U vibe-notificationthenpython -m vibe_notification --test.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file vibe_notification-1.1.0.tar.gz.
File metadata
- Download URL: vibe_notification-1.1.0.tar.gz
- Upload date:
- Size: 59.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.12.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
288ec8f876bf79dd5d3fbef045c9196e204f07cc1beb76b15c96b3122c5d77ca
|
|
| MD5 |
141b4fb32d8a205c9a1c55fa44b2e8e0
|
|
| BLAKE2b-256 |
0d5dffa7efa99d933849f2d79ef14d492416936eaae5752b5f0dc807b91429a7
|
File details
Details for the file vibe_notification-1.1.0-py3-none-any.whl.
File metadata
- Download URL: vibe_notification-1.1.0-py3-none-any.whl
- Upload date:
- Size: 64.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.12.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
884e84e0696b20a8882ec3fd9c4b91f92284ce66103414d0ee2eca41b73742da
|
|
| MD5 |
22d4a0f2d08364b3f9f004961f1fadcf
|
|
| BLAKE2b-256 |
9527f34ecdb6c27653e7744f3eba458c99b3782633dbf2a0948858b57b8c57b1
|