Skip to content

Releases: ChanMeng666/echook

v5.2.0 β€” Codex CLI Compatibility (3rd editor target)

04 May 11:49

Choose a tag to compare

Codex CLI compatibility. OpenAI's Codex CLI now joins Claude Code and Cursor IDE as a first-class editor target. Single AI-first prompt installs everything; the calling AI agent handles every follow-up step that requires a config edit. No human-in-the-loop steps.

πŸ†• For Codex users: paste this single prompt into Codex (or any AI agent that can run shell commands):

"Clone https://github.com/ChanMeng666/claude-code-audio-hooks into ~/audio-hooks, then run python ~/audio-hooks/bin/audio-hooks install --codex. Read the JSON output: if feature_flag_state is section_missing or flag_missing_or_false, follow the next_steps instruction (use your Edit tool to add [features]\ncodex_hooks = true to ~/.codex/config.toml). Then restart Codex."

What's new

audio-hooks install --codex / uninstall --codex

  • Writes $CODEX_HOME/hooks.json (default ~/.codex/hooks.json) registering the 6 events Codex supports per developers.openai.com/codex/hooks: SessionStart, PreToolUse, PostToolUse, PermissionRequest, UserPromptSubmit, Stop. The other 18 audio-hooks canonical events have no Codex equivalent and the runner no-ops them with a skipped_no_codex_equivalent debug NDJSON event.
  • Tags every entry with _managed_by: "audio-hooks" so re-installs are idempotent and uninstall preserves user-authored hooks. Never touches ~/.codex/config.toml on uninstall β€” the codex_hooks flag may benefit other Codex hook plugins.
  • Codex does NOT auto-bridge Claude Code plugins (unlike Cursor). Confirmed by independent investigation of openai/codex. Consequence: no DUPLICATE_BRIDGE problem to solve.

AI-first feature-flag handling

Codex hooks require [features]\ncodex_hooks = true in ~/.codex/config.toml. The install:

State Action
File doesn't exist Authors a fresh config.toml with the flag enabled (feature_flag_state: "freshly_written")
Flag already true Skips silently (feature_flag_state: "already_enabled")
Flag missing or false Emits a machine-readable next_steps JSON instruction so the calling AI agent can add the flag with its Edit tool

We never round-trip user-authored TOML β€” formatting and comments would be destroyed. The AI agent handles targeted edits with its Edit tool while the install handles the safe cases.

--invoker codex CLI flag

Codex sets no env var we could detect by (unlike Cursor's CURSOR_VERSION). The install template bakes --invoker codex into every command:

{ "command": "python \"/abs/path/to/hook_runner.py\" stop --invoker codex" }

New hooks/invoker.py module extracted from hook_runner.py so user_preferences.py can ask "which IDE invoked us?" without a circular import. detect_invoker() checks argv first, then env vars, then falls back to unknown. The cache is primed before argv stripping so downstream callers see the right answer.

editor_targets.codex block in audio-hooks status / manifest

Reports state (active / active-but-flag-disabled / active-but-flag-unknown / inactive), hooks_file, config_path, feature_flag_enabled, data_dir. Surfaces a CODEX_FEATURE_FLAG_MISSING warning when the install is in place but the flag isn't enabled β€” actionable for the calling AI agent.

codex: {...} sub-object in webhook payloads

When invoker == "codex", the raw payload includes a codex sub-object with turn_id, tool_use_id, permission_mode, tool_response, stop_hook_active (parallel to cursor: {...}). Schema stays at audio-hooks.webhook.v1 β€” additive, no breaking changes.

Codex-gated step in _resolve_data_dir()

New priority 3 (between env-var overrides and the plugin-cache layout): when detect_invoker() == "codex" AND $CODEX_HOME/audio-hooks-data/user_preferences.json exists, return that path. Sits ahead of the Claude Code shared dir so a developer machine that happens to have both Claude Code and Codex installed still lands at the right dir under Codex sessions.

Tests + CI

33 new bridge-contract tests in tests/test_codex_hooks.py across 8 TestCase classes:

  • Invoker detection from argv (4 cases including the --invoker=codex form and invalid-value fallback)
  • strip_invoker_args correctness
  • _resolve_data_dir Codex fallback (positive + negative gating)
  • Template validity (all 6 events, every command carries --invoker codex, every canonical handler is known)
  • Unsupported-events no-op (all 18)
  • NDJSON invoker field, webhook codex sub-object
  • Install end-to-end (writes hooks.json, substitutes paths, seeds user_preferences, writes install_marker, idempotent, preserves foreign entries)
  • Feature-flag handling (4 states)
  • Uninstall (removes managed, preserves foreign, preserves data dir by default, --purge, never touches config.toml)
  • editor_targets.codex end-to-end

135/135 tests pass on Windows.

Out of scope for 5.2.0

  • Project-scope install (<repo>/.codex/hooks.json). User-scope only is the v1 design. Users wanting per-repo audio config can hand-edit <repo>/.codex/hooks.json themselves.
  • Codex plugin packaging (~/.codex/plugins/audio-hooks/). Codex has its own plugin system but we haven't packaged audio-hooks for it; the hooks.json install is sufficient for v1.
  • Auto-editing existing ~/.codex/config.toml. Too risky for user-authored TOML β€” TOML round-trip with comment preservation is hard and getting it wrong destroys user formatting. The next_steps AI-readable instruction is the right contract for v1.

Files changed

24 files: 19 modified + 5 new. +2505 / -81. New files: codex-hooks/hooks.json, hooks/invoker.py, tests/test_codex_hooks.py, plus plugin-layout copies. See CHANGELOG.md for the full entry.

v5.1.5 β€” Painless Upgrades

01 May 10:44

Choose a tag to compare

Painless upgrades. Existing users no longer lose configuration when refreshing the plugin. New audio-hooks upgrade command does the right thing automatically; auto-migration preserves your settings when new keys are added in future versions; dual-location backups survive even rough claude plugin uninstall. Plus three behavioural reverts from 5.1.4 and a CI-enforced no-default-flip policy.

⚠️ For users who got bit by 5.1.4. If your user_preferences.json was reinitialised under 5.1.4 (e.g. via a claude plugin uninstall without --keep-data) and you now hear subagent_stop / permission_denied / task_created audio you didn't want, run:

audio-hooks hooks disable subagent_stop permission_denied task_created

Migration logic from 5.1.5 forward will preserve your choice across all future upgrades.

Upgrade path

audio-hooks upgrade --check-only      # see current vs target version
audio-hooks upgrade                   # data-preserving upgrade

audio-hooks upgrade auto-detects scope, tries claude plugin update first (data-preserving), and falls back to uninstall --keep-data + install if that fails. Crash-mid-upgrade leaves a recovery marker at ~/.claude-audio-hooks-backups/.upgrade_in_progress.json β€” re-run with --force to retry.

What's new

  • hooks/user_preferences.py β€” new module that owns every user_preferences.json access. Path resolution (6-level priority chain), load with auto-migration, save with auto-backup, atomic writes guarded by cross-platform file lock (fcntl.flock POSIX, msvcrt.locking Windows), diff_from_default() for surfacing user customizations, lazy get_prefs() singleton.
  • Auto-migration on load. When _version differs from the bundled template's, deep-merge missing keys without overwriting your values. Lists are atomic. Scalar-vs-container type mismatch resets to template default. Migration is logged to NDJSON.
  • Dual-location backups on save. Every save snapshots prior content to <data_dir>/user_preferences.json.bak (last good) AND to ~/.claude-audio-hooks-backups/<plugin_id>/<ts>.json (kept outside ~/.claude/plugins/data/ so claude plugin uninstall cannot wipe it; rotation: keep 20).
  • audio-hooks upgrade [--check-only] [--force] β€” JSON CLI verb replacing the manual two-step recipe.
  • audio-hooks backup list / show / restore / prune β€” backup management for AI operators. restore snapshots the pre-restore state first, so a wrong restore is reversible.
  • status and manifest extended with a customizations block (output of diff_from_default) showing only the keys where you differ from defaults.
  • New stable error codes: BACKUP_FAILED, BACKUP_NOT_FOUND, RESTORE_FAILED, LOCK_TIMEOUT, NOT_INSTALLED, UPGRADE_UNINSTALL_FAILED, UPGRADE_REINSTALL_FAILED, UPGRADE_VERIFY_FAILED, PRIOR_UPGRADE_INCOMPLETE.

What was fixed

  • Existing users no longer lose configuration on claude plugin uninstall + install (the path 5.1.4 documented as "do this once to refresh the cache"). Use audio-hooks upgrade instead.
  • Default value flips between versions are now CI-enforced policy violations. New tests/test_defaults_stability.py snapshots config/default_preferences.json into config/_defaults_baseline.json; flipping any existing scalar default fails CI until the baseline is also updated.
  • 5.1.4 default flips reverted. enabled_hooks.subagent_stop, enabled_hooks.permission_denied, enabled_hooks.task_created are back to false. Existing users who explicitly set these to true in 5.1.4 are unaffected β€” migration preserves user values (see warning at top for how to disable them if you want the new defaults).

Refactored

  • hooks/hook_runner.py and bin/audio-hooks.py consolidated onto UserPreferences. Removed ~200 lines of duplicated helpers (_resolve_plugin_data_dir, _is_running_from_plugin, _auto_init_user_prefs, _apply_plugin_option_overlay, the diverged _resolve_config_file/_config_path pair). Module-level globals removed. The dual-implementation drift class that produced the 5.1.4 anti-stranding bug is gone by construction.

Tests

  • 43 new unit tests across tests/test_user_preferences.py, tests/test_migration.py, tests/test_backups.py, tests/test_backup_cli.py, tests/test_upgrade_command.py, tests/test_defaults_stability.py. Stdlib-only. Total project test count: 83.

See CHANGELOG.md for the long-form notes, docs/specs/2026-05-01-painless-upgrades-design.md for the design, and docs/plans/2026-05-01-painless-upgrades-plan.md for the TDD-driven implementation plan.

v5.1.4 β€” Cursor IDE Compatibility (shared user_preferences with Claude Code)

01 May 10:47

Choose a tag to compare

[5.1.4] - 2026-05-01

⚠️ Cursor users β€” read this first. If you have audio-hooks installed in Claude Code, Cursor IDE 3.2.16+ already auto-bridges this project's hooks (per cursor.com/docs/reference/third-party-hooks) β€” Cursor's "Cursor Hooks Service" loads ~/.claude/plugins/installed_plugins.json on startup and calls our hook scripts on its own session events. This means even if you uninstalled and reinstalled Claude Code's audio-hooks, Cursor was probably still calling the old cached version of runner/run.py. To pick up the 5.1.4 fix, run inside Claude Code: /plugin uninstall audio-hooks@chanmeng-audio-hooks then /plugin install audio-hooks@chanmeng-audio-hooks. That refreshes ~/.claude/plugins/cache/chanmeng-audio-hooks/audio-hooks/<ver>/ to the new code Cursor will then bridge.

Cursor IDE compatibility. The runner now finds the user's real user_preferences.json whether it is invoked from Claude Code (which sets CLAUDE_PLUGIN_DATA) or from Cursor's auto-bridge (which does not). A new audio-hooks install --cursor subcommand registers natively for users who run Cursor without Claude Code. NDJSON events and webhook payloads now carry invoker (claude-code / cursor / unknown) plus a cursor sub-object surfacing Cursor-specific stdin fields (conversation_id, reason, final_status, duration_ms, ...). 13 new unit tests pin the contract.

Fixed

  • Cursor IDE was playing the wrong audio theme even after the user switched themes in Claude Code (reported in chat by ai@gavigo.com). Cursor's auto-bridge invokes the cached plugin's runner/run.py without setting CLAUDE_PLUGIN_DATA, so _resolve_config_file()'s legacy fallback chain in 5.1.3 returned the cached default_preferences.json (which ships audio_theme: "default") instead of the user's actual ~/.claude/plugins/data/audio-hooks-chanmeng-audio-hooks/user_preferences.json (which they had set to "custom"). The runner now has a centralized _resolve_data_dir() whose priority chain falls through to the well-known shared Claude Code data dir before the temp-dir fallback, so Cursor and Claude Code read the same preferences file. The change is backwards-compatible: behavior is unchanged when CLAUDE_PLUGIN_DATA is set.

Added

  • hooks/hook_runner.py: _resolve_data_dir() β€” single source of truth for the audio-hooks state directory, used by get_log_dir, _resolve_queue_dir, and _resolve_config_file. Priority: CLAUDE_PLUGIN_DATA β†’ CLAUDE_AUDIO_HOOKS_DATA β†’ ~/.claude/plugins/data/audio-hooks-chanmeng-audio-hooks/ (if user_preferences.json exists) β†’ ~/.cursor/audio-hooks-data/ (if user_preferences.json exists) β†’ legacy temp dir. Centralizing the chain meant the four call sites that previously had hand-coded fallbacks now agree on one definition.
  • hooks/hook_runner.py: detect_invoker() β€” returns "claude-code" / "cursor" / "unknown" based on environment variables (CURSOR_VERSION for Cursor β€” set by Cursor's bridge per cursor.com/docs/hooks; CLAUDE_PLUGIN_DATA/CLAUDE_PLUGIN_ROOT for Claude Code). Env-var detection is more reliable than parsing stdin because it survives malformed payloads.
  • session_start hook auto-emits {"env": {"CLAUDE_PLUGIN_DATA": "<path>"}} to stdout when invoker is Cursor. Per cursor.com/docs/hooks, sessionStart env outputs propagate to every subsequent hook in the same Cursor session β€” so after this one-time injection, stop, sessionEnd, preToolUse, etc. all see the correct preferences path without depending on the runtime fallback. The handler emits unconditionally regardless of enabled_hooks.session_start because env propagation is a session-setup concern, not a notification concern. Claude Code path is unaffected (no env JSON is emitted because CURSOR_VERSION is unset).
  • bin/audio-hooks install --cursor β€” writes ~/.cursor/hooks.json from cursor-hooks/hooks.json (new canonical template, schema v1, camelCase event names). Substitutes {{PYTHON}} and {{HOOK_RUNNER}} with absolute paths at install time, merges with any existing user hooks (each managed entry tagged "_managed_by": "audio-hooks" so we can find and remove only ours later), seeds ~/.cursor/audio-hooks-data/user_preferences.json from default_preferences.json, and writes ~/.cursor/audio-hooks-data/install_marker.json. Aborts with stable error code DUPLICATE_BRIDGE when Claude Code's audio-hooks plugin is already installed (because Cursor's auto-bridge would fire every event a second time); pass --force to install anyway.
  • bin/audio-hooks uninstall --cursor β€” removes only the _managed_by: "audio-hooks" entries, preserves any other user hooks, and deletes ~/.cursor/hooks.json entirely if it would be empty. Default keeps ~/.cursor/audio-hooks-data/ so re-install picks up the user's preferences; --purge removes that directory too.
  • audio-hooks status / diagnose / manifest output editor_targets β€” a per-editor JSON block reporting {state, via, ...} for claude-code and cursor. Cursor states: active / bridged-via-claude-code / native / double-registered / inactive. The double-registered state surfaces a DUPLICATE_BRIDGE warning explaining what to fix.
  • webhook_settings.include_user_email (default false) β€” opt-in flag for whether user_email from Cursor's stdin schema is included in webhook payloads. Off by default because the webhook URL may be a third-party service.
  • tests/test_cursor_bridge.py β€” 13 unit tests covering: detect_invoker env-var matrix, _resolve_data_dir priority chain, session_start env-output for Cursor (and silence for non-Cursor), NDJSON invoker field on every event, webhook payload invoker + cursor sub-object, user_email redaction by default, opt-in surfacing.
  • cursor-hooks/hooks.json β€” canonical Cursor IDE hooks template. Maps 11 supported Cursor events to our hook names. stop and subagentStop set loop_limit: 0 to defensively prevent any accidental auto-resubmission via followup_message (Cursor's loop_limit defaults to 5 for native hooks). Documents Notification and PermissionRequest as deliberately absent β€” Cursor has no equivalent events (cursor.com/docs/reference/third-party-hooks).

Documented (known limitations)

  • Cursor's bridge maps 8 of 10 Claude Code hooks. Notification and PermissionRequest have no Cursor equivalent β€” those audio cues will never fire from Cursor. Use Claude Code if you depend on them.
  • Glob / WebFetch / WebSearch matchers do not fire under Cursor β€” Cursor lacks these tool names, so pretooluse / posttooluse matchers configured for them are silently skipped by Cursor.
  • The only Cursor-side opt-out is the global "Third-party skills" toggle in Cursor Settings β€” this disables auto-bridging for all Claude Code plugins, not just audio-hooks. There is no per-plugin Cursor opt-out today.
  • Cursor caches the Claude Code plugin code under ~/.claude/plugins/cache/<id>/<ver>/. Editing this project's source under D:\github_repository\... does not propagate to Cursor until the user runs /plugin uninstall + /plugin install inside Claude Code (or otherwise refreshes the cache). This is documented in the warning at the top of this entry.

v5.1.3 β€” Clearer Context Display + UTF-8 Hardening + Statusline Tests + Debug JSON Dump

27 Apr 12:43

Choose a tag to compare

Why this release

When you /model-switch from a 1M-context variant (e.g. claude-opus-4-7[1m]) to the default 200K Sonnet 4.6, the status line could jump from Context: 17% to Context: 83% (or 97% with more context) β€” looking like a bug but actually correct math (same tokens / smaller window = larger %). v5.1.3 makes the math self-evident, hardens the contract with tests, fixes a silent crash on Windows terminals with non-UTF-8 codepages, and adds a one-flag diagnostic dump for any future status line surprise.

Fixed

  • Status line context segment is no longer opaque after /model switches (#16). The Context bar now appends absolute counts, e.g. Context: 83% (166K/200K) πŸ›‘ /compact. The numerator is derived from used_percentage Γ— context_window_size so the displayed math is always internally self-consistent. We deliberately do not use the JSON's total_input_tokens field β€” in cache-heavy sessions like Claude Code itself it counts only literal input (excluding cache_read_input_tokens / cache_creation_input_tokens) and understates real usage by ~30Γ—. Falls back silently to the pre-5.1.3 form Context: 83% when context_window_size is missing or malformed.
  • Status line silently exited empty on Windows terminals using a non-UTF-8 codepage (e.g. cp1252, cp936). The renderer prints box-drawing chars (β–ˆβ–ˆβ–‘), emoji (πŸ›‘βš οΈπŸŒΏπŸ”Š), and ANSI escapes; on a legacy codepage print() raised UnicodeEncodeError, which the outer try/except Exception swallowed, leaving users staring at a blank status line with no diagnostic. Production fix: sys.stdout.reconfigure(encoding=\"utf-8\", errors=\"replace\") at the top of main(). Surfaced by the new test suite running on Windows GH Actions runners β€” same failure mode possible on user machines with a legacy codepage.

Added

  • CLAUDE_HOOKS_DEBUG=1 (or true/yes, case-insensitive) now atomically dumps the status line stdin JSON to \${state_dir}/statusline.last_input.json. Used to diagnose what Claude Code is actually piping (e.g. confirming whether context_window_size updated after a /model change). Truthy parsing matches hook_runner.DEBUG. Privacy-noted in CLAUDE.md β€” the dump may include workspace paths and the last assistant message; disable when not actively diagnosing. Atomic per-PID tempfile + os.replace so concurrent invocations cannot leave a half-written file.
  • tests/test_statusline.py β€” 25 stdlib-only unit and integration tests, wired into the existing .github/workflows/smoke.yml 9-job matrix (Ubuntu / Windows / macOS Γ— Python 3.9 / 3.12 / 3.13). Coverage:
    • _fmt_tokens edge cases (0, sub-1K, exact 1K, exact 1M, fractional M)
    • Robustness: empty stdin, malformed JSON, null fields, string used_percentage, string / zero / negative context_window_size β€” none must crash
    • Context segment correctness: 17% on 1M (Opus pre-switch), 83% on 200K (Sonnet post-switch), red-threshold /compact hint
    • Regression guard that the script does NOT use total_input_tokens as the numerator
    • CLAUDE_HOOKS_DEBUG toggle parity with hook_runner (1/true/yes enable; 0/false/no/anything else disable)
    • Atomic-rename hygiene (no leftover .tmp files)
    • The test harness pre-populates the statusline cache and pins PYTHONIOENCODING=utf-8 β€” necessary on Windows runners and good cross-platform practice.

Changed

  • bin/audio-hooks-statusline.py main() now calls _force_utf8_stdout() before any other work β€” a tiny sys.stdout.reconfigure(encoding='utf-8', errors='replace') wrapper that degrades silently on the off chance reconfigure isn't available. Defensive against locale mismatches on user machines.
  • bin/audio-hooks-statusline.py:_maybe_dump_session truthy parsing was tightened from strict \"1\" to {1, true, yes} (case-insensitive) to match hook_runner.DEBUG.
  • Documentation: CLAUDE.md decision tree gains entries for "context jumped to 83% / 97% after I switched models" (explains it is expected) and "diagnose what Claude Code is sending to the status line" (points at CLAUDE_HOOKS_DEBUG). The env-var table updates CLAUDE_HOOKS_DEBUG to mention both the NDJSON log and the status line dump. New docs/TROUBLESHOOTING.md section with the same explanation. README and docs/ARCHITECTURE.md examples show the new format. Plugin SKILL.md adds a "Diagnosing the status line" section so AI agents can resolve future similar reports without re-investigating.

Upgrade

Plugin install β€” auto-updates on next claude launch (the status line registration points directly at the file path in the cloned repo / installed plugin):

```bash

Verify current version

audio-hooks version # should report 5.1.3
audio-hooks status # confirms statusline is installed
```

Script install (legacy) β€” re-run `bash scripts/install-complete.sh` to refresh the copies under `~/.claude/`.

Compatibility

Fully backwards-compatible. The new (X/Y) display only appears when Claude Code's stdin JSON includes both context_window.used_percentage and context_window.context_window_size (newer Claude Code releases). Older Claude Code versions that only send used_percentage continue to render the pre-5.1.3 Context: 83% form. Windows users on legacy codepages who were experiencing silent status line failures will see correct output after upgrading.

Verified

10/10 CI jobs green: Ubuntu / Windows / macOS Γ— Python 3.9 / 3.12 / 3.13 + plugin-in-sync.

πŸ€– Generated with Claude Code

v5.1.2 β€” fix Windows audio truncation

20 Apr 10:11
eee6b11

Choose a tag to compare

Patch release. Fixes a long-standing Windows-only audio-playback bug where the default permission-request.mp3 (~3.4 s) and elicitation.mp3 (~3.1 s) were always cut off ~0.4 s before the end. Affects every user on the default (voice) theme on Windows or WSL. Custom chime theme was unaffected (all chime files < 3 s).

Upgrading

claude plugin update audio-hooks@chanmeng-audio-hooks
# then in the Claude Code REPL:
/reload-plugins

Verify with audio-hooks version (should report 5.1.2).

What changed

Fixed

  • Windows audio playback truncated clips > 3 s (#14, #15). Every PowerShell playback snippet in hooks/hook_runner.py β€” three fallback methods in play_audio_windows and the WSL path in play_audio_wsl β€” used a fixed Start-Sleep -Seconds 3 before calling $player.Stop(); $player.Close(). The bundled permission-request.mp3 is 3 396 ms, so PowerShell tore the player down ~396 ms before the clip finished. Exactly the "~0.4 s consistently cut off" described in the bug report.

    Fix: poll the media player for the actual clip length and sleep for duration + 500 ms tail buffer before tearing down. PresentationCore MediaPlayer polls $player.NaturalDuration.HasTimeSpan with a 1.5 s ceiling (Open() is async), then uses TotalMilliseconds. WMPlayer.OCX polls $w.currentMedia.duration. If the player never reports a duration (corrupt file, etc.), fallback is Start-Sleep -Seconds 10 β€” generous enough for any plausible clip, still bounded.

    The Python subprocess.Popen layer was already fire-and-forget; the fix closes the gap that was inside the PowerShell command string. macOS afplay and Linux mpg123/ffplay/paplay/aplay are unaffected β€” those players block until playback completes by default.

Empirical validation

Measured on Windows 11 against audio/default/permission-request.mp3:

Metric v5.1.1 v5.1.2
MediaPlayer.NaturalDuration reported by the player 3 396 ms 3 396 ms
PowerShell lifetime (time until $player.Stop()) ~3 000 ms ~3 915 ms
Audio truncated ~396 ms 0 ms

Version alignment

Every version reference bumped to 5.1.2:

File Value
hooks/hook_runner.py HOOK_RUNNER_VERSION 5.1.2
bin/audio-hooks.py PROJECT_VERSION 5.1.2
.claude-plugin/marketplace.json (metadata + plugin entry) 5.1.2
plugins/audio-hooks/.claude-plugin/plugin.json 5.1.2
config/default_preferences.json (_version + version) 5.1.2
CLAUDE.md header + Version-history row 5.1.2
README.md badge + sequence diagram + status-line example 5.1.2
docs/ARCHITECTURE.md status-line example 5.1.2
plugins/audio-hooks/skills/audio-hooks/SKILL.md status-line example 5.1.2

Credit

Bug reported in #14 by @Basdanucha β€” thanks for the precise analysis (file size, bitrate, the three exact line numbers, and three concrete fix options to choose between). Made the fix a ~20-minute job.

Full changelog: https://github.com/ChanMeng666/claude-code-audio-hooks/blob/master/CHANGELOG.md#512---2026-04-20

v5.1.1 β€” critical import crash fix + CI smoke tests

17 Apr 12:33
8727011

Choose a tag to compare

Critical patch release. Everyone on v5.0.3 or v5.1.0 must upgrade β€” those releases crashed at import time and every audio-hooks subcommand failed.

Upgrading

claude plugin update audio-hooks@chanmeng-audio-hooks
# then in the Claude Code REPL:
/reload-plugins

Verify with audio-hooks version (should report 5.1.1) and audio-hooks diagnose.

What changed

Fixed

  • NameError: name 'Tuple' is not defined at hook_runner.py import time (#10, #11). The runner used Tuple in module-level annotations on SYNTHETIC_EVENT_MAP and _resolve_synthetic_event but never imported it from typing. With no from __future__ import annotations in the file, CPython evaluated the annotations at import time and every audio-hooks subcommand β€” diagnose, status, version, test, hook dispatch β€” crashed before it could do anything. One-line fix: add Tuple to the existing typing import.

Added

  • CI import-smoke workflow (#12, .github/workflows/smoke.yml). Runs on every push to master and every PR across 9 jobs (Ubuntu / Windows / macOS Γ— Python 3.9 / 3.12 / 3.13), plus a plugin-in-sync job. Exercises:

    • import hook_runner on both the canonical hooks/ and the synced plugins/audio-hooks/hooks/ copy
    • audio-hooks version / status / diagnose
    • audio-hooks test all β€” all 26 hooks dispatch
    • scripts/build-plugin.sh --check β€” canonical/plugin drift detection

    Designed to catch exactly the class of bug that shipped in v5.0.3 / v5.1.0.

Version alignment

Prior to v5.1.1, the v5.1.0 tag was cut from the "context window monitor" feature commit without bumping the in-tree version strings, so v5.1.0 shipped reporting itself as 5.0.3. v5.1.1 makes every version reference consistent:

File Value
hooks/hook_runner.py HOOK_RUNNER_VERSION 5.1.1
bin/audio-hooks.py PROJECT_VERSION 5.1.1
.claude-plugin/marketplace.json (metadata + plugin entry) 5.1.1
plugins/audio-hooks/.claude-plugin/plugin.json 5.1.1
config/default_preferences.json (_version + version) 5.1.1
CLAUDE.md header, README.md badge, docs status line examples 5.1.1

Credit

Bug reported in #10 β€” thanks to the reporter for the precise repro (line numbers, root cause analysis, and verified one-line patch).

Full changelog: https://github.com/ChanMeng666/claude-code-audio-hooks/blob/master/CHANGELOG.md#511---2026-04-18

v5.1.0 β€” Context Window Monitor & Customisable Status Line

12 Apr 23:11

Choose a tag to compare

What's new

Context window monitor in the status line

The status line now tracks context window usage in real time with color-coded thresholds that warn you before entering the "agent dumb zone" β€” when context exceeds 60–70%, Claude starts ignoring instructions and making basic coding errors.

Color Range What it means What to do
🟒 Green < 50% Safe β€” agent performs well Nothing, keep working
🟑 Yellow 50–80% Caution β€” entering the "dumb zone" Type /compact or /clear
πŸ”΄ Red > 80% Danger β€” agent makes frequent errors Type /compact immediately
[Opus] πŸ”Š Audio Hooks v5.1.0 | 6/26 Sounds | Webhook: off | Theme: Voice
🌿 main  β–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘β–‘β–‘ API Quota: 60%  β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘β–‘ Context: 65% ⚠️ /compact

10 customisable status line segments

Users can choose exactly which segments to show. Just tell Claude Code in natural language:

  • "Only show context usage in the status line" β†’ shows just the context bar
  • "Show context and API quota" β†’ shows two progress bars
  • "Show everything" β†’ resets to all 10 segments (default)

Available segments: model, version, sounds, webhook, theme, snooze, focus, branch, api_quota, context.

audio-hooks set statusline_settings.visible_segments '["context"]'
audio-hooks set statusline_settings.visible_segments '["context","api_quota"]'
audio-hooks set statusline_settings.visible_segments '[]'   # show all (default)

Human-readable display labels (global accessibility)

All status line labels redesigned so non-English-native, non-technical users can understand them at a glance:

Before After
ctx: 65% Context: 65%
5h: 60% API Quota: 60%
24/26 hooks 24/26 Sounds
theme: custom Theme: Chimes
theme: default Theme: Voice
SNOOZED MUTED

Old config segment names (ctx, hooks, rate_limit) are still accepted for backwards compatibility.

SKILL.md now covers all features

The /audio-hooks SKILL (the natural-language interface Claude Code uses) now documents every project capability. 8 previously missing sections added:

  • Check project status β€” audio-hooks status / audio-hooks version
  • Notification settings β€” audio-only, notification-only, per-hook overrides
  • Playback settings β€” debounce configuration
  • Focus Flow β€” breathing exercises, min thinking seconds
  • Uninstall β€” plugin and script paths
  • Generic get/set β€” read/write any config key
  • Webhook view β€” audio-hooks webhook (view current config)
  • Status line customisation β€” segment selection with examples

Install / upgrade

If you already have the plugin installed, Claude Code will pick up the new version automatically on next plugin update. For new installs:

Tell Claude Code: "Install the audio-hooks plugin from github.com/ChanMeng666/claude-code-audio-hooks"

Then type /reload-plugins once. That's it β€” natural language forever after.

Files changed

  • bin/audio-hooks-statusline.py β€” context bar, segment filtering, human-readable labels, backwards-compat alias system
  • bin/audio-hooks.py β€” cmd_status now emits statusline_settings
  • config/default_preferences.json β€” new statusline_settings.visible_segments key
  • plugins/audio-hooks/skills/audio-hooks/SKILL.md β€” full feature coverage, 8 new sections
  • CLAUDE.md β€” updated config table and decision tree
  • README.md β€” new status line section with mermaid diagram, segment table, examples
  • docs/ARCHITECTURE.md β€” updated statusline documentation

Full Changelog: v5.0.3...v5.1.0

v5.0.3 β€” Documentation correction: /reload-plugins is the one manual slash command

11 Apr 04:02

Choose a tag to compare

⚠ This is a documentation-only patch. No code changes. v5.0.2's README and release notes overclaimed that "the AI inside Claude Code runs the entire install autonomously". The user immediately caught this during real testing: the AI cannot invoke /reload-plugins (or any other slash command) because slash commands are interactive REPL primitives with no CLI equivalent and no Skill tool exposure. The user has to type /reload-plugins themselves once after install. v5.0.3 fixes the docs to be honest about this.

If you installed v5.0.2 successfully, you don't need to do anything β€” the runtime code is unchanged. v5.0.3 only updates README, CLAUDE.md, CHANGELOG, and the plugin manifest version field.


The honest install flow (4 user actions)

1. Open Claude Code (1 shell command)
   β†’ type `claude` in any terminal

2. Ask Claude Code to install (1 natural-language prompt)
   β†’ "Install the audio-hooks plugin from ChanMeng666/claude-code-audio-hooks
      by running `claude plugin marketplace add ChanMeng666/claude-code-audio-hooks`
      then `claude plugin install audio-hooks@chanmeng-audio-hooks` via Bash.
      Tell me to type /reload-plugins when it's done."
   β†’ Claude Code runs both Bash commands and reports back

3. Type /reload-plugins (1 manual slash command β€” THE ONLY ONE)
   β†’ This activates the plugin's hooks. There is no CLI equivalent for this
     command β€” it's REPL-only β€” so you must type it yourself.

4. Ask Claude Code to verify and configure (1 natural-language prompt)
   β†’ "Verify with `audio-hooks diagnose` and `audio-hooks test all`,
      then switch to the chime audio theme."
   β†’ Claude Code runs all three commands via Bash and reports back

Steps 2 and 4 are pure natural language. Steps 1 and 3 are unavoidable because they're entry/reload primitives the AI literally has no tool to invoke.

After step 4, every subsequent operation is pure natural language forever. You never type another command. You say "snooze for an hour", "why is there no sound", "send notifications to my Slack", and Claude Code translates each request into the right audio-hooks subcommand.


What the AI actually can and cannot do (verified end-to-end on Claude Code v2.1.101)

βœ… The AI inside Claude Code CAN run via the Bash tool

Operation CLI command
Add a plugin marketplace claude plugin marketplace add <source>
List configured marketplaces claude plugin marketplace list
Remove a marketplace claude plugin marketplace remove <name>
Update marketplace metadata claude plugin marketplace update [name]
Install a plugin claude plugin install <plugin>
Uninstall a plugin claude plugin uninstall <plugin>
Enable/disable a plugin claude plugin enable|disable <plugin>
Update a plugin claude plugin update <plugin>
List installed plugins claude plugin list
Validate a plugin manifest claude plugin validate <path>
Every audio-hooks subcommand audio-hooks status | diagnose | test | theme | snooze | webhook | tts | rate-limits | hooks list | set | get | logs | install | uninstall | statusline | version | manifest

❌ The AI CANNOT invoke (REPL-only, no tool exposure)

Slash command Why it can't be automated
/reload-plugins Interactive REPL command. Reads keystrokes from the human at the prompt. Has no CLI equivalent (verified via claude --help and claude plugin --help) and is not registered as a Skill.
/exit Same.
/clear Same.
/doctor Same.
Any other built-in slash command The Skill tool's documentation is explicit: "Do not use this tool for built-in CLI commands (like /help, /clear, etc.)". Built-in REPL commands are not skills and have no programmatic invocation path.

Why the previous overclaim happened

I assumed that because the SKILL system can invoke skill-defined commands (e.g. the plugin's own /audio-hooks SKILL), the same mechanism could invoke other slash commands like /reload-plugins. It cannot. The Skill tool only invokes user-defined skills, not built-in REPL commands.

The user's pushback caught the overclaim within hours of the v5.0.2 release. Lesson recorded: don't claim a workflow is fully automated until you've verified every step against a real install, not just a mental model. The AI control surface is still genuinely powerful for everything that has a CLI command, which is every other operation in this project β€” but /reload-plugins is the one gap that this release acknowledges in writing.


What changed in v5.0.3

Documentation only β€” no code changes. The runtime behavior is identical to v5.0.2.

  • README.md (834 lines)

    • Tagline rewritten: "You type one slash command at install time. Then natural language forever."
    • "AI-first way" section restructured to 4 honest steps with the manual /reload-plugins step prominently labeled.
    • Mermaid sequence diagram updated: the manual /reload-plugins step is now highlighted in a contrasting color block.
    • "Why this matters" section adds an explicit honesty paragraph listing what the AI can and cannot run.
    • "Design philosophy" section's natural-language operating model paragraph rewritten: "the human types one slash command in their lifetime with this project."
  • CLAUDE.md (323 lines)

    • "AI quickstart" rewritten as step-by-step instructions for Claude Code itself when operating the project on a human's behalf.
    • New "Critical" warning to Claude Code: "Do NOT pretend you can run /reload-plugins via the Bash tool β€” you cannot. Tell the user to type it, wait for them to confirm, then continue with verification."
    • Decision tree install row updated to: "Run claude plugin marketplace add and claude plugin install via the Bash tool. Then ask the user to type /reload-plugins (you cannot run this β€” REPL only)."
  • CHANGELOG.md v5.0.3 entry with full lists of AI-can/AI-cannot, plus the explanation of why the overclaim happened.

  • Version bumped 5.0.2 β†’ 5.0.3 across hook_runner.py, bin/audio-hooks.py, marketplace.json, plugin.json, default_preferences.json, README.md, CLAUDE.md. The bump exists only so the manifest version reflects the doc fix.


Upgrading from v5.0.2

Inside Claude Code, ask:

Update audio-hooks to v5.0.3 by running claude plugin update audio-hooks@chanmeng-audio-hooks. Then tell me to type /reload-plugins.

Then type /reload-plugins. Done β€” same content as v5.0.2 plus the corrected docs.

If you installed via the legacy script install path, run:

git pull
bash scripts/install-complete.sh

The script auto-detects non-TTY and installs without prompts.


Documentation

Diff from v5.0.2: v5.0.2...v5.0.3 (docs only, no code changes)

Full v5.0 line diff: v4.7.2...v5.0.3

v5.0.2 β€” AI-first redesign + post-install bugfix patch

11 Apr 03:42

Choose a tag to compare

⚠ CORRECTION (see v5.0.3): the headline tagline below ("You never type a command") is wrong. The AI inside Claude Code cannot invoke /reload-plugins (or any other interactive REPL slash command) because slash commands have no CLI equivalent and no Skill tool exposure. The honest install flow is 4 user actions: 1 shell command + 1 natural-language prompt + 1 manual /reload-plugins + 1 natural-language prompt. Steps 2 and 4 are pure natural language; steps 1 and 3 are unavoidable. After install, every other operation IS pure natural language forever β€” but the install itself is not 100% automated. v5.0.3 fixes the docs to be honest about this. The runtime code in v5.0.2 is unchanged and still correct β€” only the docs were wrong. Read the v5.0.3 release notes for the corrected install flow.


You never type a command. You never edit a config file. You never read a log.
You open Claude Code, say "install audio hooks", and Claude Code does everything else β€” install the plugin, register the 26 hook events, fetch the audio files, configure your preferences, and verify it works. From then on, you operate the project entirely through natural language: "snooze for an hour", "switch to chimes", "send notifications to my Slack", "why is there no sound". Claude Code translates each request into the right audio-hooks subcommand and reports back. The human is the requester. Claude Code is the operator.

This release rolls v5.0.0 (the AI-first redesign), v5.0.1 (the ElevenLabs audio addition), and v5.0.2 (the post-install bugfix patch + README rewrite) into one shippable artifact. It is the first release of the v5.0 line that has been fully verified end-to-end on a real Claude Code v2.1.101 install.


πŸ€– Install in one sentence

Open Claude Code (claude in any terminal) and paste this into the prompt:

Please install the claude-code-audio-hooks plugin from github.com/ChanMeng666/claude-code-audio-hooks. Add the marketplace, install the plugin, run /reload-plugins to activate it, then run audio-hooks diagnose and audio-hooks test all to verify everything works. Tell me when it is ready.

That is it. You do not clone the repo. You do not run any bash command. You do not type any slash command. Claude Codes plugin system fetches everything from this GitHub repo, registers all 26 hook events, plays a smoke test of every audio file, and reports back in plain English when it is done.

Then operate the project the same way:

You say What Claude Code runs
"snooze audio for an hour" audio-hooks snooze 1h
"switch to chime audio" audio-hooks theme set custom
"send alerts to my Slack at https://hooks.slack.com/..." audio-hooks webhook set --url ... --format slack then webhook test
"speak Claudes actual reply when done" audio-hooks tts set --enabled true --speak-assistant-message true
"why is there no sound" audio-hooks diagnose then runs the suggested fix for each error

See the README "AI-first way" section for the full prompt catalogue.


What changed across the v5.0 line

v5.0.0 β€” AI-first redesign

The defining release. Re-frames every project surface around Claude Code as the operator, not a human user.

Added:

  • bin/audio-hooks JSON CLI with 27 subcommands. Default invocation returns the canonical machine manifest. No prompts, no colors, no spinners β€” JSON to stdout, errors with stable codes + suggested commands. Subcommands: manifest, manifest --schema, status, version, get, set, hooks list/enable/disable/enable-only, theme list/set, snooze, webhook set/clear/test, tts set, rate-limits set, test, diagnose, logs tail/clear, install, uninstall, update, statusline show/install/uninstall.
  • /audio-hooks SKILL for natural-language activation. Trigger phrases like "snooze audio", "why is there no sound", "send notifications to Slack" activate the SKILL, which loads a structured guide telling Claude Code exactly which subcommand to run.
  • NDJSON structured logging at ${CLAUDE_PLUGIN_DATA}/logs/events.ndjson with stable audio-hooks.v1 schema. 16 stable error code enums, each with hint and suggested_command fields. Log rotation: 5 MB cap, 3 files kept. Legacy debug.log, errors.log, hook_triggers.log removed.
  • Claude Code plugin packaging: .claude-plugin/marketplace.json, plugins/audio-hooks/ self-contained layout, matcher-scoped hooks/hooks.json, plugin SKILL, bin/ on PATH, bundled audio. Install via /plugin marketplace add ChanMeng666/claude-code-audio-hooks then /plugin install audio-hooks@chanmeng-audio-hooks.
  • Four new hook events: PermissionDenied (auto mode classifier denials), CwdChanged (working directory changes), FileChanged (watched file changes β€” matcher takes literal filenames), TaskCreated (sibling of TaskCompleted). Hook count is now 26.
  • Native matcher routing via synthetic event names. hooks/hooks.json registers per-matcher handlers like session_start_resume, stop_failure_rate_limit, notification_idle_prompt instead of one wildcard handler per event. Per-variant audio overrides. Faster, configurable per-matcher, and per-handler async: true means a slow rate-limit-failure path does not block the auth-failure path.
  • New stdin field parsing: last_assistant_message (Stop, SubagentStop), worktree.{name,branch,path}, agent_id, agent_type, notification_type, source (SessionStart), error_type (StopFailure), trigger (PreCompact/PostCompact), load_reason (InstructionsLoaded), permission_suggestions (PermissionRequest). Universal context suffix [session: foo, worktree: bar, agent: baz] appended to every notification.
  • TTS speak Claudes actual reply: audio-hooks tts set --speak-assistant-message true β€” instead of speaking "Task completed", TTS the truncated last_assistant_message from the stop hook. Off by default for privacy.
  • Rate-limit pre-check: inspects every hooks stdin rate_limits.{five_hour,seven_day}.used_percentage and plays a one-shot warning audio at configurable thresholds (default [80, 95]). Marker-debounced per (window, threshold, resets_at) so each reset window can re-fire each threshold exactly once.
  • Fire-and-forget webhook subprocess dispatch: spawns a tiny detached Python subprocess that does the urlopen and exits, so the parent hook process can exit immediately even on slow webhooks. Versioned audio-hooks.webhook.v1 payload surfaces every new stdin field as a top-level key.
  • Status line script bin/audio-hooks-statusline with two-line layout (model + version + enabled-hook count + theme on line 1, conditional snooze indicator + focus-flow indicator + worktree branch + colored rate-limit progress bar on line 2). Auto-registered via audio-hooks statusline install with refreshInterval: 60.
  • Non-interactive scripts: install-complete.sh, install-windows.ps1, uninstall.sh, quick-setup.sh all auto-engage non-interactive mode when stdin is not a TTY or CLAUDE_NONINTERACTIVE=1 is set. uninstall.sh defaults to preserve config and audio (less destructive); pass --purge to remove them. Human-only menu scripts (configure.sh, test-audio.sh) emit a structured INTERACTIVE_SCRIPT JSON pointer when invoked non-interactively, redirecting to the audio-hooks CLI.
  • CLAUDE.md rewritten as the canonical AI doc with operating principles, three-command quickstart, full hook catalogue, decision tree, error code reference, env var reference.

v5.0.1 β€” ElevenLabs audio addition

Added:

  • Eight dedicated audio files for the v5.0 hooks, generated via the ElevenLabs API:
    • audio/default/{permission-denied,cwd-changed,file-changed,task-created}.mp3 (Jessica voice TTS)
    • audio/custom/chime-{permission-denied,cwd-changed,file-changed,task-created}.mp3 (sound-generation API)
  • scripts/generate-audio.py β€” non-interactive Python generator. Reads config/audio_manifest.json (the single source of truth for every audio files text prompt + voice + theme + sound-effect parameters) and regenerates any subset via the ElevenLabs API. Default behavior: skip files that already exist. Flags: --force, --only filename1,filename2, --dry-run. Reads ELEVENLABS_API_KEY from environment, never writes the key to disk. Output is NDJSON per file plus a final summary JSON. Stdlib-only, no third-party dependencies.
  • config/audio_manifest.json β€” single source of truth for audio file generation prompts, voice IDs, sound-effect parameters, and the TTS model.
  • CLAUDE_PLUGIN_OPTION_* env var overlay in both hook_runner.py and bin/audio-hooks. The plugin manifests userConfig declarations now flow from Claude Codes plugin install through to the runtime config without writing to user_preferences.json. Webhook auto-enables when a URL is supplied via the env var.

v5.0.2 β€” post-install bugfix patch + AI-first README rewrite

The first end-to-end install of v5.0.1 on a real Claude Code v2.1.101 session surfaced five real bugs that were invisible from outside an actual install. v5.0.2 fixes all of them and rewrites the public README + docs to lead with the projects defining selling point: users never type a command, they just talk to Claude Code in natural language.

Fixed:

  • userConfig schema rejected by claude plugin validate (fefe2c9). The v5.0.1 manifest used description+sensitive fields per my earlier read of the docs; the validator actually requires type (one of string|number|boolean|directory|file) plus title. Without this fix the plugin failed t...
Read more

v4.7.2 - Performance Optimization: Faster Hook Execution

23 Mar 10:41

Choose a tag to compare

Performance Optimization

Optimizes hook_runner.py critical path to reduce execution overhead, especially for disabled hooks (18 of 22 by default).

Changes

  • Config caching: load_config() reads disk once per invocation instead of 3Γ— β€” eliminates redundant JSON parsing
  • Lazy queue directory: QUEUE_DIR.mkdir() deferred from module import to first actual use
  • Deferred auto-update: check_and_self_update() moved after enabled/snoozed/debounced/filtered gates β€” disabled hooks skip it entirely
  • Context caching: get_notification_context() called once, reused for desktop notification, TTS, and webhook channels
  • Webhook guard: send_webhook() only called when webhook_settings.enabled is true

Performance Impact

Scenario Before After Improvement
Disabled hook ~25-35ms ~5-10ms 3-5Γ— faster
Enabled hook (audio only) ~40-60ms ~30-45ms ~25% faster
Enabled hook (all channels) ~50-80ms ~45-70ms ~10% faster

Zero Functional Changes

All 22 hooks, all config options, all platforms, all features (audio, notifications, TTS, webhooks, Focus Flow, filters, snooze, debounce) work exactly as before. This is a pure performance optimization with no behavioral changes.

Full Changelog: v4.7.1...v4.7.2