From 83a0f681592d758db51befb1b39edf26c3b75af4 Mon Sep 17 00:00:00 2001
From: Chan Meng
Date: Sat, 18 Apr 2026 00:28:47 +1200
Subject: [PATCH 1/2] =?UTF-8?q?chore(release):=20v5.1.1=20=E2=80=94=20fix?=
=?UTF-8?q?=20critical=20import=20crash,=20add=20CI=20smoke=20test?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
v5.1.1 is a critical patch release. v5.0.3 and v5.1.0 both shipped with a
broken hook_runner.py that crashed at import time with `NameError: name
'Tuple' is not defined`, blocking every audio-hooks subcommand. The fix
itself landed in #11 (commit 885c3e1); this release packages it and adds
regression-prevention CI.
Changes in this commit:
- Bump every version reference to 5.1.1 and realign them:
* hooks/hook_runner.py (HOOK_RUNNER_VERSION)
* bin/audio-hooks.py (PROJECT_VERSION)
* .claude-plugin/marketplace.json (metadata + plugin entry)
* plugins/audio-hooks/.claude-plugin/plugin.json
* config/default_preferences.json (_version + version)
* CLAUDE.md header
* README.md version badge + sequence diagram + status line example
* docs/ARCHITECTURE.md status line example
* plugins/audio-hooks/skills/audio-hooks/SKILL.md status line example
- Add .github/workflows/smoke.yml: CI import-smoke tests on push to master
and on every PR. 3x3 matrix (Ubuntu/Windows/macOS x Python 3.9/3.12/3.13)
exercises `import hook_runner` for both canonical and plugin copies,
`audio-hooks version/status/diagnose`, `audio-hooks test all` (26/26),
and `scripts/build-plugin.sh --check` for canonical/plugin drift.
- Add CHANGELOG.md entry for 5.1.1 explaining the fix, the new CI, and
why v5.1.0 shipped under the 5.0.3 version string.
- Sync canonical changes into plugins/audio-hooks/ via
`bash scripts/build-plugin.sh` (affects hook_runner.py, audio-hooks.py,
default_preferences.json copies).
Verified locally: `audio-hooks version` reports 5.1.1, `audio-hooks test
all` passes 26/26, `build-plugin.sh --check` reports in_sync.
Co-Authored-By: Claude Opus 4.7 (1M context)
---
.claude-plugin/marketplace.json | 4 +-
.github/workflows/smoke.yml | 50 +++++++++++++++++++
CHANGELOG.md | 20 ++++++++
CLAUDE.md | 2 +-
README.md | 6 +--
bin/audio-hooks.py | 2 +-
config/default_preferences.json | 6 +--
docs/ARCHITECTURE.md | 2 +-
hooks/hook_runner.py | 2 +-
.../audio-hooks/.claude-plugin/plugin.json | 2 +-
plugins/audio-hooks/bin/audio-hooks.py | 2 +-
.../config/default_preferences.json | 6 +--
plugins/audio-hooks/hooks/hook_runner.py | 2 +-
.../audio-hooks/skills/audio-hooks/SKILL.md | 2 +-
14 files changed, 89 insertions(+), 19 deletions(-)
create mode 100644 .github/workflows/smoke.yml
diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json
index 2f32b28..8eee1bd 100644
--- a/.claude-plugin/marketplace.json
+++ b/.claude-plugin/marketplace.json
@@ -6,13 +6,13 @@
},
"metadata": {
"description": "AI-operated audio notification system for Claude Code. 26 hooks, native matcher routing, TTS, webhook fan-out, status line, focus flow, rate-limit alerts.",
- "version": "5.0.3"
+ "version": "5.1.1"
},
"plugins": [
{
"name": "audio-hooks",
"source": "./plugins/audio-hooks",
- "version": "5.0.3",
+ "version": "5.1.1",
"description": "Audio notifications + AI-controlled config for every Claude Code event",
"author": {
"name": "Chan Meng",
diff --git a/.github/workflows/smoke.yml b/.github/workflows/smoke.yml
new file mode 100644
index 0000000..b6567ea
--- /dev/null
+++ b/.github/workflows/smoke.yml
@@ -0,0 +1,50 @@
+name: smoke
+
+on:
+ push:
+ branches: [master]
+ pull_request:
+
+jobs:
+ import-smoke:
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, windows-latest, macos-latest]
+ python-version: ["3.9", "3.12", "3.13"]
+ runs-on: ${{ matrix.os }}
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-python@v5
+ with:
+ python-version: ${{ matrix.python-version }}
+
+ - name: Import canonical hook_runner
+ shell: bash
+ run: |
+ python -c "import sys; sys.path.insert(0,'hooks'); import hook_runner; print('canonical', hook_runner.HOOK_RUNNER_VERSION)"
+
+ - name: Import plugin hook_runner copy
+ shell: bash
+ run: |
+ python -c "import sys; sys.path.insert(0,'plugins/audio-hooks/hooks'); import hook_runner; print('plugin', hook_runner.HOOK_RUNNER_VERSION)"
+
+ - name: audio-hooks version / status / diagnose
+ shell: bash
+ run: |
+ python bin/audio-hooks.py version
+ python bin/audio-hooks.py status
+ python bin/audio-hooks.py diagnose
+
+ - name: audio-hooks test all (every hook dispatches)
+ shell: bash
+ run: |
+ python bin/audio-hooks.py test all
+
+ plugin-in-sync:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - name: Verify plugin copy matches canonical sources
+ run: bash scripts/build-plugin.sh --check
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cb677bb..0e192e1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,26 @@ All notable changes to Claude Code Audio Hooks will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [5.1.1] - 2026-04-18
+
+Critical import-time crash fix plus regression-prevention CI. Everyone on v5.0.3 or v5.1.0 should upgrade.
+
+### Fixed
+
+- **`hook_runner.py` crashed on import with `NameError: name 'Tuple' is not defined`** ([#10](https://github.com/ChanMeng666/claude-code-audio-hooks/issues/10)). The file used `Tuple` in module-level type annotations (`SYNTHETIC_EVENT_MAP` and `_resolve_synthetic_event`) but did not import it from `typing`. Because the module has no `from __future__ import annotations`, CPython evaluated the annotations at import time and every `audio-hooks` subcommand (`diagnose`, `status`, `version`, `test`, …) crashed before dispatch. Users on v5.0.3 and v5.1.0 were fully blocked. One-line fix: add `Tuple` to the existing `typing` import.
+
+### Added
+
+- **CI import-smoke workflow** (`.github/workflows/smoke.yml`) to prevent regressions of this class of bug. Runs on every push to `master` and every PR across a 3×3 matrix (Ubuntu / Windows / macOS × Python 3.9 / 3.12 / 3.13) and exercises:
+ - `import hook_runner` from 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 successfully)
+ - `scripts/build-plugin.sh --check` (the plugin copy is in sync with canonical sources)
+
+### Note on v5.1.0
+
+The `v5.1.0` tag was cut from the "context window monitor" feature commit but the in-tree version strings, CHANGELOG, and release notes were never bumped, so v5.1.0 shipped with the broken import under the 5.0.3 version string. v5.1.1 corrects this: every version reference (`HOOK_RUNNER_VERSION`, `PROJECT_VERSION`, `marketplace.json`, `plugin.json`, `config/default_preferences.json`, `CLAUDE.md` header) is now consistently `5.1.1`.
+
## [5.0.3] - 2026-04-11
Documentation correction. v5.0.2's README and release notes overclaimed that *"the human never types a command — Claude Code does everything"*. The user immediately caught the overclaim during real testing: the AI inside a Claude Code session **cannot** invoke `/reload-plugins` (or any other slash command) because slash commands are interactive REPL primitives with no CLI equivalent and no tool exposure. The user has to type `/reload-plugins` themselves once after install.
diff --git a/CLAUDE.md b/CLAUDE.md
index d6e30f5..55f2b6b 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -1,6 +1,6 @@
# Claude Code Audio Hooks — AI Operator Guide
-> **Version:** 5.0.3 | **Last Updated:** 2026-04-11
+> **Version:** 5.1.1 | **Last Updated:** 2026-04-18
This document is the canonical machine-facing guide for the claude-code-audio-hooks project. It is written for **Claude Code (and other AI agents) as the operator** — not for humans. A human user says "install audio hooks for me" or "snooze audio for an hour" and you do everything via the project's structured CLI without any human-in-the-loop confirmation, menu navigation, or log file reading.
diff --git a/README.md b/README.md
index d71c483..c573fff 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@ You type one slash command at install time. Then natural language forever.
26 hook events, 2 audio themes, rate-limit alerts, webhooks, TTS, context monitor — all operated by Claude Code on your behalf.
[](https://opensource.org/licenses/MIT)
-[](https://github.com/ChanMeng666/claude-code-audio-hooks)
+[](https://github.com/ChanMeng666/claude-code-audio-hooks)
[](https://github.com/ChanMeng666/claude-code-audio-hooks)
[](https://claude.ai/download)
[](#-install-in-60-seconds)
@@ -286,7 +286,7 @@ sequenceDiagram
You->>CC: Show me the last 20 errors and clear the log.
CC-->>You: 2 errors found (WEBHOOK_TIMEOUT). Log cleared.
You->>CC: What version of audio-hooks am I running?
- CC-->>You: v5.0.3, plugin install.
+ CC-->>You: v5.1.1, plugin install.
You->>CC: Please uninstall audio-hooks completely.
CC-->>You: Plugin uninstalled. All hooks removed.
end
@@ -505,7 +505,7 @@ Real-time context window and API quota bars — color-coded warnings before Clau
```text
-[Opus] Audio Hooks v5.0.3 | 6/26 Sounds | Webhook: ntfy | Theme: Voice
+[Opus] Audio Hooks v5.1.1 | 6/26 Sounds | Webhook: ntfy | Theme: Voice
[MUTED 23m] feat/audio-v5 API Quota: 78% Context: 65% /compact
```
diff --git a/bin/audio-hooks.py b/bin/audio-hooks.py
index 51973de..0f057e6 100644
--- a/bin/audio-hooks.py
+++ b/bin/audio-hooks.py
@@ -132,7 +132,7 @@ def require_project_root() -> int:
# Project state — version, install detection, hook catalogue
# ---------------------------------------------------------------------------
-PROJECT_VERSION = "5.0.3"
+PROJECT_VERSION = "5.1.1"
# Canonical hook catalogue. Order matches CLAUDE.md and the install scripts.
HOOK_CATALOG: List[Dict[str, Any]] = [
diff --git a/config/default_preferences.json b/config/default_preferences.json
index 21a9e8d..86edfc4 100644
--- a/config/default_preferences.json
+++ b/config/default_preferences.json
@@ -1,10 +1,10 @@
{
"$schema": "./user_preferences.schema.json",
- "_comment": "Claude Code Audio Hooks - Default Configuration Template (v5.0.3)",
- "_version": "5.0.3",
+ "_comment": "Claude Code Audio Hooks - Default Configuration Template (v5.1.1)",
+ "_version": "5.1.1",
"_description": "This file is the default template for user_preferences.json. Plugin installs auto-copy this to ${CLAUDE_PLUGIN_DATA}/user_preferences.json on first read. AI agents should NOT edit this file directly — use 'audio-hooks set ' instead.",
- "version": "5.0.3",
+ "version": "5.1.1",
"_comment_audio_theme": "Audio theme: 'default' for voice recordings, 'custom' for non-voice chimes. Change this one line to switch all audio.",
"audio_theme": "default",
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
index fadf25a..2b719fc 100644
--- a/docs/ARCHITECTURE.md
+++ b/docs/ARCHITECTURE.md
@@ -207,7 +207,7 @@ Native matcher routing happens at the `settings.json` layer (Claude Code's match
Two-line bottom bar registered in `~/.claude/settings.json` via `audio-hooks statusline install`. Reads stdin JSON Claude Code provides (model name, session_id, workspace.git_worktree, rate_limits, context_window) and emits two lines of plain text with ANSI colors:
```text
-[Opus] 🔊 Audio Hooks v5.0.3 | 6/26 Sounds | Webhook: ntfy | Theme: Voice
+[Opus] 🔊 Audio Hooks v5.1.1 | 6/26 Sounds | Webhook: ntfy | Theme: Voice
[MUTED 23m] 🌿 feat/audio-v5 ████░░░░ API Quota: 78% █████░░░ Context: 65% ⚠️ /compact
```
diff --git a/hooks/hook_runner.py b/hooks/hook_runner.py
index ea854df..c03aaf4 100644
--- a/hooks/hook_runner.py
+++ b/hooks/hook_runner.py
@@ -31,7 +31,7 @@
# Version used for auto-sync: when the installed copy in ~/.claude/hooks/
# detects a newer version in the project directory, it self-updates.
-HOOK_RUNNER_VERSION = "5.0.3"
+HOOK_RUNNER_VERSION = "5.1.1"
# =============================================================================
# STRUCTURED LOGGING (NDJSON)
diff --git a/plugins/audio-hooks/.claude-plugin/plugin.json b/plugins/audio-hooks/.claude-plugin/plugin.json
index 15ce5ad..4a9e93e 100644
--- a/plugins/audio-hooks/.claude-plugin/plugin.json
+++ b/plugins/audio-hooks/.claude-plugin/plugin.json
@@ -1,6 +1,6 @@
{
"name": "audio-hooks",
- "version": "5.0.3",
+ "version": "5.1.1",
"description": "Audio notifications + AI-controlled config for every Claude Code event. 26 hooks, native matcher routing, TTS, webhooks, status line, focus flow, rate-limit alerts.",
"author": {
"name": "Chan Meng",
diff --git a/plugins/audio-hooks/bin/audio-hooks.py b/plugins/audio-hooks/bin/audio-hooks.py
index 51973de..0f057e6 100644
--- a/plugins/audio-hooks/bin/audio-hooks.py
+++ b/plugins/audio-hooks/bin/audio-hooks.py
@@ -132,7 +132,7 @@ def require_project_root() -> int:
# Project state — version, install detection, hook catalogue
# ---------------------------------------------------------------------------
-PROJECT_VERSION = "5.0.3"
+PROJECT_VERSION = "5.1.1"
# Canonical hook catalogue. Order matches CLAUDE.md and the install scripts.
HOOK_CATALOG: List[Dict[str, Any]] = [
diff --git a/plugins/audio-hooks/config/default_preferences.json b/plugins/audio-hooks/config/default_preferences.json
index 21a9e8d..86edfc4 100644
--- a/plugins/audio-hooks/config/default_preferences.json
+++ b/plugins/audio-hooks/config/default_preferences.json
@@ -1,10 +1,10 @@
{
"$schema": "./user_preferences.schema.json",
- "_comment": "Claude Code Audio Hooks - Default Configuration Template (v5.0.3)",
- "_version": "5.0.3",
+ "_comment": "Claude Code Audio Hooks - Default Configuration Template (v5.1.1)",
+ "_version": "5.1.1",
"_description": "This file is the default template for user_preferences.json. Plugin installs auto-copy this to ${CLAUDE_PLUGIN_DATA}/user_preferences.json on first read. AI agents should NOT edit this file directly — use 'audio-hooks set ' instead.",
- "version": "5.0.3",
+ "version": "5.1.1",
"_comment_audio_theme": "Audio theme: 'default' for voice recordings, 'custom' for non-voice chimes. Change this one line to switch all audio.",
"audio_theme": "default",
diff --git a/plugins/audio-hooks/hooks/hook_runner.py b/plugins/audio-hooks/hooks/hook_runner.py
index ea854df..c03aaf4 100644
--- a/plugins/audio-hooks/hooks/hook_runner.py
+++ b/plugins/audio-hooks/hooks/hook_runner.py
@@ -31,7 +31,7 @@
# Version used for auto-sync: when the installed copy in ~/.claude/hooks/
# detects a newer version in the project directory, it self-updates.
-HOOK_RUNNER_VERSION = "5.0.3"
+HOOK_RUNNER_VERSION = "5.1.1"
# =============================================================================
# STRUCTURED LOGGING (NDJSON)
diff --git a/plugins/audio-hooks/skills/audio-hooks/SKILL.md b/plugins/audio-hooks/skills/audio-hooks/SKILL.md
index af51cd8..b4377ce 100644
--- a/plugins/audio-hooks/skills/audio-hooks/SKILL.md
+++ b/plugins/audio-hooks/skills/audio-hooks/SKILL.md
@@ -110,7 +110,7 @@ The status line displays real-time audio-hooks state and context window usage at
After installing, the status line updates every 60 seconds and shows two lines:
```
-[Opus] 🔊 Audio Hooks v5.0.3 | 6/26 Sounds | Webhook: off | Theme: Voice
+[Opus] 🔊 Audio Hooks v5.1.1 | 6/26 Sounds | Webhook: off | Theme: Voice
🌿 main ████░░░░ API Quota: 60% █████░░░ Context: 65% ⚠️ /compact
```
From 8c3c2af01f0f414943fa5b617907e9ff1418aaf2 Mon Sep 17 00:00:00 2001
From: Chan Meng
Date: Sat, 18 Apr 2026 00:30:45 +1200
Subject: [PATCH 2/2] ci(smoke): install mpg123 on Linux runners so diagnose
passes
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The `audio-hooks diagnose` command correctly exits non-zero when no audio
player is present (AUDIO_PLAYER_NOT_FOUND). GitHub's Ubuntu runners ship
without one, so every Linux job failed on the diagnose step even though
the actual code under test was fine. macOS has afplay built-in and
Windows has PowerShell's MediaPlayer, so those pass naturally.
Install mpg123 — the player the project recommends in its own
AUDIO_PLAYER_NOT_FOUND hint — before running the subcommand suite.
Co-Authored-By: Claude Opus 4.7 (1M context)
---
.github/workflows/smoke.yml | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/.github/workflows/smoke.yml b/.github/workflows/smoke.yml
index b6567ea..9b02fcb 100644
--- a/.github/workflows/smoke.yml
+++ b/.github/workflows/smoke.yml
@@ -20,6 +20,10 @@ jobs:
with:
python-version: ${{ matrix.python-version }}
+ - name: Install Linux audio player
+ if: runner.os == 'Linux'
+ run: sudo apt-get update && sudo apt-get install -y mpg123
+
- name: Import canonical hook_runner
shell: bash
run: |