Skip to main content

VibeNotification

PyPI Python Platform License

English | 中文

Stop waiting when vibe coding — Give a notification when Claude Code or Codex finishes replies —

Blog walkthrough (Chinese): AI应用系列 一个简单的Vibe coding的通知系统

image-20251221214216954

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

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 SessionEnd or SubagentStop: VibeNotification ignores them by default to avoid duplicate alerts from session-exit, subagent, or tool-chain lifecycle events.
  • On macOS, VibeNotification now defaults to sender off in Claude Code hook contexts and terminal-hosted CLI contexts for more reliable banners. If you explicitly want host-app attribution/icon, set VIBE_NOTIFICATION_SENDER_MODE=force.
  • If a notification appears only in Notification Center, check System Settings > Notifications for the effective app (terminal-notifier when 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.json and 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.01.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 to terminal-notifier. The notification is still sent and is normally attributed to terminal-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 like off.
  • 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 older config.example.json files and existing configs remains supported for compatibility.
  • Keep detect_conversation_end set to true. When disabled, ordinary non-terminal events may follow legacy notification behavior, although explicitly suppressed safety events remain silent.
  • notification_timeout is 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.01.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

  1. Bump the version in pyproject.toml (single source of truth).
  2. Install tooling: python -m pip install --upgrade build twine.
  3. Build: python -m build (creates .tar.gz and .whl under dist/).
  4. Validate: python -m twine check dist/*.
  5. Upload: TWINE_USERNAME=__token__ TWINE_PASSWORD=<pypi-token> python -m twine upload dist/* (use --repository testpypi to dry run).
  6. Install + verify: pip install -U vibe-notification then python -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

vibe_notification-1.1.0.tar.gz (59.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

vibe_notification-1.1.0-py3-none-any.whl (64.6 kB view details)

Uploaded Python 3

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

Hashes for vibe_notification-1.1.0.tar.gz
Algorithm Hash digest
SHA256 288ec8f876bf79dd5d3fbef045c9196e204f07cc1beb76b15c96b3122c5d77ca
MD5 141b4fb32d8a205c9a1c55fa44b2e8e0
BLAKE2b-256 0d5dffa7efa99d933849f2d79ef14d492416936eaae5752b5f0dc807b91429a7

See more details on using hashes here.

File details

Details for the file vibe_notification-1.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for vibe_notification-1.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 884e84e0696b20a8882ec3fd9c4b91f92284ce66103414d0ee2eca41b73742da
MD5 22d4a0f2d08364b3f9f004961f1fadcf
BLAKE2b-256 9527f34ecdb6c27653e7744f3eba458c99b3782633dbf2a0948858b57b8c57b1

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

1.1.0 This release

2 files

1.0.26

2 files

1.0.25

2 files

1.0.23

2 files

1.0.22

2 files

1.0.21

2 files

1.0.19

2 files

1.0.18

2 files

1.0.17

2 files

1.0.16

2 files

1.0.14

2 files

1.0.13

2 files

1.0.9

2 files

1.0.5

2 files

1.0.4

2 files

1.0.3

2 files

1.0.2

2 files

1.0.1

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page