Skip to content

Instantly share code, notes, and snippets.

@stormychel
Last active May 25, 2026 09:16
Show Gist options
  • Select an option

  • Save stormychel/502e2a6da38bf5798750e5565a5324f7 to your computer and use it in GitHub Desktop.

Select an option

Save stormychel/502e2a6da38bf5798750e5565a5324f7 to your computer and use it in GitHub Desktop.
[macOS] Claude Code Stop hook — announces 'Claude is done' via the macOS Zarvox voice (say -v Zarvox). Windows sibling: https://gist.github.com/stormychel/a2b6a437873b62b4e9750186babcc658
#!/usr/bin/env python3
"""
[macOS] Install a Claude Code "Stop" hook that announces completion out loud
with the macOS Zarvox voice — i.e. it runs `say -v Zarvox "Claude is done"`
every time Claude finishes a response.
Windows sibling (SAPI / David voice):
https://gist.github.com/stormychel/a2b6a437873b62b4e9750186babcc658
System-wide: writes to ~/.claude/settings.json (merges, preserves existing keys).
Idempotent: re-running won't add a duplicate.
Usage:
python3 install-claude-zarvox-hook.py
Disable later: remove the entry from the "hooks" -> "Stop" array in settings.json.
Change the phrase/voice: edit CMD below (run `say -v '?'` to list voices).
"""
import json
import pathlib
CMD = 'say -v Zarvox "Claude is done"'
def main() -> None:
settings = pathlib.Path.home() / ".claude" / "settings.json"
cfg = json.loads(settings.read_text()) if settings.exists() else {}
stop = cfg.setdefault("hooks", {}).setdefault("Stop", [])
already = any(
hook.get("command") == CMD
for entry in stop
for hook in entry.get("hooks", [])
)
if not already:
stop.append({"hooks": [{"type": "command", "command": CMD}]})
settings.parent.mkdir(parents=True, exist_ok=True)
settings.write_text(json.dumps(cfg, indent=2))
print(f"✅ Zarvox Stop hook {'already present' if already else 'installed'} at {settings}")
if __name__ == "__main__":
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment