Releases: ChanMeng666/echook
v5.2.0 β Codex CLI Compatibility (3rd editor target)
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: iffeature_flag_stateissection_missingorflag_missing_or_false, follow thenext_stepsinstruction (use your Edit tool to add[features]\ncodex_hooks = trueto~/.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 askipped_no_codex_equivalentdebug 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.tomlon uninstall β thecodex_hooksflag 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_BRIDGEproblem 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=codexform and invalid-value fallback) strip_invoker_argscorrectness_resolve_data_dirCodex 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
invokerfield, 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.codexend-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.jsonthemselves. - 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. Thenext_stepsAI-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
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 youruser_preferences.jsonwas reinitialised under 5.1.4 (e.g. via aclaude plugin uninstallwithout--keep-data) and you now hearsubagent_stop/permission_denied/task_createdaudio you didn't want, run:audio-hooks hooks disable subagent_stop permission_denied task_createdMigration 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 upgradeaudio-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 everyuser_preferences.jsonaccess. Path resolution (6-level priority chain), load with auto-migration, save with auto-backup, atomic writes guarded by cross-platform file lock (fcntl.flockPOSIX,msvcrt.lockingWindows),diff_from_default()for surfacing user customizations, lazyget_prefs()singleton.- Auto-migration on load. When
_versiondiffers 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/soclaude plugin uninstallcannot 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.restoresnapshots the pre-restore state first, so a wrong restore is reversible.statusandmanifestextended with acustomizationsblock (output ofdiff_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"). Useaudio-hooks upgradeinstead. - Default value flips between versions are now CI-enforced policy violations. New
tests/test_defaults_stability.pysnapshotsconfig/default_preferences.jsonintoconfig/_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_createdare back tofalse. Existing users who explicitly set these totruein 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.pyandbin/audio-hooks.pyconsolidated ontoUserPreferences. 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_pathpair). 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)
[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.jsonon 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 ofrunner/run.py. To pick up the 5.1.4 fix, run inside Claude Code:/plugin uninstall audio-hooks@chanmeng-audio-hooksthen/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.pywithout settingCLAUDE_PLUGIN_DATA, so_resolve_config_file()'s legacy fallback chain in 5.1.3 returned the cacheddefault_preferences.json(which shipsaudio_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 whenCLAUDE_PLUGIN_DATAis set.
Added
hooks/hook_runner.py: _resolve_data_dir()β single source of truth for the audio-hooks state directory, used byget_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/(ifuser_preferences.jsonexists) β~/.cursor/audio-hooks-data/(ifuser_preferences.jsonexists) β 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_VERSIONfor Cursor β set by Cursor's bridge per cursor.com/docs/hooks;CLAUDE_PLUGIN_DATA/CLAUDE_PLUGIN_ROOTfor Claude Code). Env-var detection is more reliable than parsing stdin because it survives malformed payloads.session_starthook auto-emits{"env": {"CLAUDE_PLUGIN_DATA": "<path>"}}to stdout when invoker is Cursor. Per cursor.com/docs/hooks,sessionStartenv 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 ofenabled_hooks.session_startbecause env propagation is a session-setup concern, not a notification concern. Claude Code path is unaffected (no env JSON is emitted becauseCURSOR_VERSIONis unset).bin/audio-hooks install --cursorβ writes~/.cursor/hooks.jsonfromcursor-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.jsonfromdefault_preferences.json, and writes~/.cursor/audio-hooks-data/install_marker.json. Aborts with stable error codeDUPLICATE_BRIDGEwhen Claude Code's audio-hooks plugin is already installed (because Cursor's auto-bridge would fire every event a second time); pass--forceto install anyway.bin/audio-hooks uninstall --cursorβ removes only the_managed_by: "audio-hooks"entries, preserves any other user hooks, and deletes~/.cursor/hooks.jsonentirely if it would be empty. Default keeps~/.cursor/audio-hooks-data/so re-install picks up the user's preferences;--purgeremoves that directory too.audio-hooks status/diagnose/manifestoutputeditor_targetsβ a per-editor JSON block reporting{state, via, ...}forclaude-codeandcursor. Cursor states:active/bridged-via-claude-code/native/double-registered/inactive. Thedouble-registeredstate surfaces aDUPLICATE_BRIDGEwarning explaining what to fix.webhook_settings.include_user_email(defaultfalse) β opt-in flag for whetheruser_emailfrom 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_invokerenv-var matrix,_resolve_data_dirpriority chain,session_startenv-output for Cursor (and silence for non-Cursor), NDJSONinvokerfield on every event, webhook payloadinvoker+cursorsub-object,user_emailredaction by default, opt-in surfacing.cursor-hooks/hooks.jsonβ canonical Cursor IDE hooks template. Maps 11 supported Cursor events to our hook names.stopandsubagentStopsetloop_limit: 0to defensively prevent any accidental auto-resubmission viafollowup_message(Cursor's loop_limit defaults to 5 for native hooks). DocumentsNotificationandPermissionRequestas 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.
NotificationandPermissionRequesthave no Cursor equivalent β those audio cues will never fire from Cursor. Use Claude Code if you depend on them. Glob/WebFetch/WebSearchmatchers do not fire under Cursor β Cursor lacks these tool names, sopretooluse/posttoolusematchers 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 underD:\github_repository\...does not propagate to Cursor until the user runs/plugin uninstall+/plugin installinside 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
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
/modelswitches (#16). The Context bar now appends absolute counts, e.g.Context: 83% (166K/200K) π /compact. The numerator is derived fromused_percentage Γ context_window_sizeso the displayed math is always internally self-consistent. We deliberately do not use the JSON'stotal_input_tokensfield β in cache-heavy sessions like Claude Code itself it counts only literal input (excludingcache_read_input_tokens/cache_creation_input_tokens) and understates real usage by ~30Γ. Falls back silently to the pre-5.1.3 formContext: 83%whencontext_window_sizeis 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 codepageprint()raisedUnicodeEncodeError, which the outertry/except Exceptionswallowed, leaving users staring at a blank status line with no diagnostic. Production fix:sys.stdout.reconfigure(encoding=\"utf-8\", errors=\"replace\")at the top ofmain(). 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(ortrue/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 whethercontext_window_sizeupdated after a/modelchange). Truthy parsing matcheshook_runner.DEBUG. Privacy-noted inCLAUDE.mdβ the dump may include workspace paths and the last assistant message; disable when not actively diagnosing. Atomic per-PID tempfile +os.replaceso 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.yml9-job matrix (Ubuntu / Windows / macOS Γ Python 3.9 / 3.12 / 3.13). Coverage:_fmt_tokensedge cases (0, sub-1K, exact 1K, exact 1M, fractional M)- Robustness: empty stdin, malformed JSON,
nullfields, stringused_percentage, string / zero / negativecontext_window_sizeβ none must crash - Context segment correctness: 17% on 1M (Opus pre-switch), 83% on 200K (Sonnet post-switch), red-threshold
/compacthint - Regression guard that the script does NOT use
total_input_tokensas the numerator CLAUDE_HOOKS_DEBUGtoggle parity withhook_runner(1/true/yesenable;0/false/no/anything else disable)- Atomic-rename hygiene (no leftover
.tmpfiles) - 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.pymain() now calls_force_utf8_stdout()before any other work β a tinysys.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_sessiontruthy parsing was tightened from strict\"1\"to{1, true, yes}(case-insensitive) to matchhook_runner.DEBUG.- Documentation:
CLAUDE.mddecision 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 atCLAUDE_HOOKS_DEBUG). The env-var table updatesCLAUDE_HOOKS_DEBUGto mention both the NDJSON log and the status line dump. Newdocs/TROUBLESHOOTING.mdsection with the same explanation. README anddocs/ARCHITECTURE.mdexamples show the new format. PluginSKILL.mdadds 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
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-pluginsVerify 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 inplay_audio_windowsand the WSL path inplay_audio_wslβ used a fixedStart-Sleep -Seconds 3before calling$player.Stop(); $player.Close(). The bundledpermission-request.mp3is 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 mstail buffer before tearing down. PresentationCoreMediaPlayerpolls$player.NaturalDuration.HasTimeSpanwith a 1.5 s ceiling (Open()is async), then usesTotalMilliseconds. WMPlayer.OCX polls$w.currentMedia.duration. If the player never reports a duration (corrupt file, etc.), fallback isStart-Sleep -Seconds 10β generous enough for any plausible clip, still bounded.The Python
subprocess.Popenlayer was already fire-and-forget; the fix closes the gap that was inside the PowerShell command string. macOSafplayand Linuxmpg123/ffplay/paplay/aplayare 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
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-pluginsVerify with audio-hooks version (should report 5.1.1) and audio-hooks diagnose.
What changed
Fixed
NameError: name 'Tuple' is not definedathook_runner.pyimport time (#10, #11). The runner usedTuplein module-level annotations onSYNTHETIC_EVENT_MAPand_resolve_synthetic_eventbut never imported it fromtyping. With nofrom __future__ import annotationsin the file, CPython evaluated the annotations at import time and everyaudio-hookssubcommand βdiagnose,status,version,test, hook dispatch β crashed before it could do anything. One-line fix: addTupleto the existingtypingimport.
Added
-
CI import-smoke workflow (#12,
.github/workflows/smoke.yml). Runs on every push tomasterand every PR across 9 jobs (Ubuntu / Windows / macOS Γ Python 3.9 / 3.12 / 3.13), plus aplugin-in-syncjob. Exercises:import hook_runneron both the canonicalhooks/and the syncedplugins/audio-hooks/hooks/copyaudio-hooks version/status/diagnoseaudio-hooks test allβ all 26 hooks dispatchscripts/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
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 systembin/audio-hooks.pyβcmd_statusnow emitsstatusline_settingsconfig/default_preferences.jsonβ newstatusline_settings.visible_segmentskeyplugins/audio-hooks/skills/audio-hooks/SKILL.mdβ full feature coverage, 8 new sectionsCLAUDE.mdβ updated config table and decision treeREADME.mdβ new status line section with mermaid diagram, segment table, examplesdocs/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
β 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-pluginsthemselves 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-pluginsstep prominently labeled. - Mermaid sequence diagram updated: the manual
/reload-pluginsstep 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-pluginsvia 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 addandclaude plugin installvia the Bash tool. Then ask the user to type/reload-plugins(you cannot run this β REPL only)."
-
CHANGELOG.mdv5.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.shThe script auto-detects non-TTY and installs without prompts.
Documentation
- README.md β public introduction with the corrected AI-first install path
- CLAUDE.md β canonical AI-facing operating guide
- docs/ARCHITECTURE.md β developer-facing architecture deep dive
- CHANGELOG.md β full version history
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
β 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 rightaudio-hookssubcommand 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-pluginsto activate it, then runaudio-hooks diagnoseandaudio-hooks test allto 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-hooksJSON 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-hooksSKILL 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.ndjsonwith stableaudio-hooks.v1schema. 16 stable error code enums, each withhintandsuggested_commandfields. Log rotation: 5 MB cap, 3 files kept. Legacydebug.log,errors.log,hook_triggers.logremoved. - Claude Code plugin packaging:
.claude-plugin/marketplace.json,plugins/audio-hooks/self-contained layout, matcher-scopedhooks/hooks.json, plugin SKILL,bin/on PATH, bundled audio. Install via/plugin marketplace add ChanMeng666/claude-code-audio-hooksthen/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 ofTaskCompleted). Hook count is now 26. - Native matcher routing via synthetic event names.
hooks/hooks.jsonregisters per-matcher handlers likesession_start_resume,stop_failure_rate_limit,notification_idle_promptinstead of one wildcard handler per event. Per-variant audio overrides. Faster, configurable per-matcher, and per-handlerasync: truemeans 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 truncatedlast_assistant_messagefrom the stop hook. Off by default for privacy. - Rate-limit pre-check: inspects every hooks stdin
rate_limits.{five_hour,seven_day}.used_percentageand 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.v1payload surfaces every new stdin field as a top-level key. - Status line script
bin/audio-hooks-statuslinewith 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 viaaudio-hooks statusline installwithrefreshInterval: 60. - Non-interactive scripts:
install-complete.sh,install-windows.ps1,uninstall.sh,quick-setup.shall auto-engage non-interactive mode when stdin is not a TTY orCLAUDE_NONINTERACTIVE=1is set.uninstall.shdefaults to preserve config and audio (less destructive); pass--purgeto remove them. Human-only menu scripts (configure.sh,test-audio.sh) emit a structuredINTERACTIVE_SCRIPTJSON pointer when invoked non-interactively, redirecting to theaudio-hooksCLI. CLAUDE.mdrewritten 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. Readsconfig/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. ReadsELEVENLABS_API_KEYfrom 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 bothhook_runner.pyandbin/audio-hooks. The plugin manifestsuserConfigdeclarations now flow from Claude Codes plugin install through to the runtime config without writing touser_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:
userConfigschema rejected byclaude plugin validate(fefe2c9). The v5.0.1 manifest useddescription+sensitivefields per my earlier read of the docs; the validator actually requirestype(one ofstring|number|boolean|directory|file) plustitle. Without this fix the plugin failed t...
v4.7.2 - Performance Optimization: Faster Hook Execution
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 whenwebhook_settings.enabledis 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