diff --git a/.claude/hooks/HOOKS-README.md b/.claude/hooks/HOOKS-README.md index 270ae03..0cbe6c1 100644 --- a/.claude/hooks/HOOKS-README.md +++ b/.claude/hooks/HOOKS-README.md @@ -1,39 +1,74 @@ # HOOKS-README contains all the details, scripts, and instructions for the hooks -## Hook Events Overview - [Official 16 Hooks](https://docs.claude.com/en/docs/claude-code/hooks-guide) +## Hook Events Overview - [Official 18 Hooks](https://code.claude.com/docs/en/hooks) Claude Code provides several hook events that run at different points in the workflow: -1. SessionStart: Runs when Claude Code starts a new session or resumes one -2. SessionEnd: Runs when a Claude Code session ends -3. UserPromptSubmit: Runs when the user submits a prompt -4. PreToolUse: Runs before tool calls (can block them) -5. PostToolUse: Runs after tool calls complete -6. PostToolUseFailure: Runs after tool calls fail -7. PermissionRequest: Runs when Claude asks for a permission decision -8. Notification: Runs when Claude Code sends notifications -9. Stop: Runs when Claude Code finishes responding -10. SubagentStart: Runs when a subagent task begins -11. SubagentStop: Runs when a subagent task completes -12. PreCompact: Runs before Claude runs context compaction -13. Setup: Runs during setup/maintenance workflows -14. TeammateIdle: Runs when an Agent Team teammate goes idle -15. TaskCompleted: Runs when a tracked task reaches completion -16. ConfigChange: Fires when configuration files change during a session, for enterprise security auditing + +| # | Hook | Description | Options | +|:-:|------|-------------|---------| +| 1 | `PreToolUse` | Runs before tool calls (can block them) | `async`, `timeout: 5000` | +| 2 | `PermissionRequest` | Runs when Claude Code requests permission from the user | `async`, `timeout: 5000`, `permission_suggestions` | +| 3 | `PostToolUse` | Runs after tool calls complete successfully | `async`, `timeout: 5000`, `tool_response` | +| 4 | `PostToolUseFailure` | Runs after tool calls fail | `async`, `timeout: 5000`, `error`, `is_interrupt` | +| 5 | `UserPromptSubmit` | Runs when the user submits a prompt, before Claude processes it | `async`, `timeout: 5000`, `prompt` | +| 6 | `Notification` | Runs when Claude Code sends notifications | `async`, `timeout: 5000`, `notification_type`, `message`, `title` | +| 7 | `Stop` | Runs when Claude Code finishes responding | `async`, `timeout: 5000`, `last_assistant_message`, `stop_hook_active` | +| 8 | `SubagentStart` | Runs when subagent tasks start | `async`, `timeout: 5000`, `agent_id`, `agent_type` | +| 9 | `SubagentStop` | Runs when subagent tasks complete | `async`, `timeout: 5000`, `agent_id`, `agent_type`, `last_assistant_message`, `agent_transcript_path`, `stop_hook_active` | +| 10 | `PreCompact` | Runs before Claude Code is about to run a compact operation | `async`, `timeout: 5000`, `once`, `trigger`, `custom_instructions` | +| 11 | `SessionStart` | Runs when Claude Code starts a new session or resumes an existing session | `async`, `timeout: 5000`, `once`, `agent_type`, `model`, `source` | +| 12 | `SessionEnd` | Runs when Claude Code session ends | `async`, `timeout: 5000`, `once`, `reason` | +| 13 | `Setup` | Runs when Claude Code runs the /setup command for project initialization | `async`, `timeout: 30000` | +| 14 | `TeammateIdle` | Runs when a teammate agent becomes idle (experimental agent teams) | `async`, `timeout: 5000`, `teammate_name`, `team_name` | +| 15 | `TaskCompleted` | Runs when a background task completes (experimental agent teams) | `async`, `timeout: 5000`, `task_id`, `task_subject`, `task_description`, `teammate_name`, `team_name` | +| 16 | `ConfigChange` | Runs when a configuration file changes during a session | `async`, `timeout: 5000`, `file_path`, `source` | +| 17 | `WorktreeCreate` | Runs when agent worktree isolation creates worktrees for custom VCS setup | `async`, `timeout: 5000`, `name` | +| 18 | `WorktreeRemove` | Runs when agent worktree isolation removes worktrees for custom VCS teardown | `async`, `timeout: 5000`, `worktree_path` | + +> **Note:** Hooks 14-15 (`TeammateIdle` and `TaskCompleted`) require the experimental agent teams feature. Set `CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1` when launching Claude Code to enable them. + +### Not in Official Docs + +The following items exist in the [Claude Code Changelog](https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md) but are **not listed** in the [Official Hooks Reference](https://code.claude.com/docs/en/hooks): + +| Item | Added In | Changelog Reference | Notes | +|------|----------|-------------------|-------| +| `Setup` hook | [v2.1.10](https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md#2110) | "Added new Setup hook event that can be triggered via `--init`, `--init-only`, or `--maintenance` CLI flags for repository setup and maintenance operations" | Not listed in official hooks reference page (17 hooks listed, Setup excluded) | +| Agent frontmatter hooks | [v2.1.0](https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md#210) | "Added hooks support to agent frontmatter, allowing agents to define PreToolUse, PostToolUse, and Stop hooks scoped to the agent's lifecycle" | Changelog only mentions 3 hooks, but testing confirms **6 hooks** actually fire in agent sessions: PreToolUse, PostToolUse, PermissionRequest, PostToolUseFailure, Stop, SubagentStop. Not all 16 hooks are supported. | ## Prerequisites -Before using hooks, ensure you have the following prerequisites installed for your operating system: +Before using hooks, ensure you have **Python 3** installed on your system. -#### Windows -- **Python**: `python --version` +### Required Software -#### macOS -- **Python 3**: `python3 --version` -- **Audio Player**: `afplay` (built-in, no installation needed) +#### All Platforms (Windows, macOS, Linux) +- **Python 3**: Required for running the hook scripts +- Verify installation: `python3 --version` -#### Linux -- **Python 3**: `python3 --version` -- **Audio Player**: `sudo apt install pulseaudio-utils` +**Installation Instructions:** +- **Windows**: Download from [python.org](https://www.python.org/downloads/) or install via `winget install Python.Python.3` +- **macOS**: Install via `brew install python3` (requires [Homebrew](https://brew.sh/)) +- **Linux**: Install via `sudo apt install python3` (Ubuntu/Debian) or `sudo yum install python3` (RHEL/CentOS) + +### Audio Players (Optional - Automatically Detected) + +The hook scripts automatically detect and use the appropriate audio player for your platform: + +- **macOS**: Uses `afplay` (built-in, no installation needed) +- **Linux**: Uses `paplay` from `pulseaudio-utils` - install via `sudo apt install pulseaudio-utils` +- **Windows**: Uses built-in `winsound` module (included with Python) + +### How Hooks Are Executed + +The hooks are configured in `.claude/settings.json` to run directly with Python 3: + +```json +{ + "type": "command", + "command": "python3 .claude/hooks/scripts/hooks.py" +} +``` ## Configuring Hooks (Enable/Disable) @@ -50,6 +85,8 @@ Edit `.claude/settings.local.json` and set: **Note:** The `.claude/settings.local.json` file is git-ignored, so each user can configure their own hook preferences without affecting the team's shared settings in `.claude/settings.json`. +> **Managed Settings:** If an administrator has configured hooks through managed policy settings, `disableAllHooks` set in user, project, or local settings cannot disable those managed hooks (fixed in v2.1.49). + ### Disable Individual Hooks For granular control, you can disable specific hooks by editing the hooks configuration files. @@ -69,41 +106,366 @@ Edit `.claude/hooks/config/hooks-config.json` for team-wide defaults: ```json { - "disableSessionStartHook": false, - "disableSessionEndHook": false, - "disableUserPromptSubmitHook": false, + "disableLogging": false, "disablePreToolUseHook": false, + "disablePermissionRequestHook": false, "disablePostToolUseHook": false, "disablePostToolUseFailureHook": false, - "disablePermissionRequestHook": false, + "disableUserPromptSubmitHook": false, "disableNotificationHook": false, - "disableSubagentStartHook": false, "disableStopHook": false, + "disableSubagentStartHook": false, "disableSubagentStopHook": false, "disablePreCompactHook": false, + "disableSessionStartHook": false, + "disableSessionEndHook": false, "disableSetupHook": false, "disableTeammateIdleHook": false, "disableTaskCompletedHook": false, "disableConfigChangeHook": false, - "disableLogging": true + "disableWorktreeCreateHook": false, + "disableWorktreeRemoveHook": false } ``` +**Configuration Options:** +- `disableLogging`: Set to `true` to disable logging hook events to `.claude/hooks/logs/hooks-log.jsonl` (useful to prevent log file growth) + #### Local Configuration (Personal Overrides) Create or edit `.claude/hooks/config/hooks-config.local.json` for personal preferences: ```json { + "disableLogging": true, "disablePostToolUseHook": true, "disableSessionStartHook": true } ``` -In this example, only the PostToolUse and SessionStart hooks are overridden locally. All other hooks will use the shared configuration values. +In this example, logging is disabled, and the PostToolUse and SessionStart hooks are overridden locally. All other hooks will use the shared configuration values. -**Note:** Individual hook toggles are checked by the hook script (`.claude/hooks/scripts/hooks.py`). Local settings override shared settings, and if a hook is disabled, the script exits silently without playing sounds or executing hook logic. +**Note:** Individual hook toggles are checked by the hook script (`.claude/hooks/scripts/hooks.py`). Local settings override shared settings, and if a hook is disabled, the script exits silently without playing any sounds or executing hook logic. ### Text to Speech (TTS) website used to generate sounds: https://elevenlabs.io/ voice used: Samara X + +## Agent Frontmatter Hooks + +Claude Code 2.1.0 introduced support for agent-specific hooks defined in agent frontmatter files. These hooks only run within the agent's lifecycle and support a subset of hook events. + +### Supported Agent Hooks + +Agent frontmatter hooks support **6 hooks** (not all 16). The changelog originally mentioned only 3, but testing confirms 6 hooks actually fire in agent sessions: +- `PreToolUse`: Runs before the agent uses a tool +- `PostToolUse`: Runs after the agent completes a tool use +- `PermissionRequest`: Runs when a tool requires user permission +- `PostToolUseFailure`: Runs after a tool call fails +- `Stop`: Runs when the agent finishes +- `SubagentStop`: Runs when a subagent completes + +> **Note:** The [v2.1.0 changelog](https://github.com/anthropics/claude-code/blob/main/CHANGELOG.md#210) only mentions 3 hooks: *"Added hooks support to agent frontmatter, allowing agents to define PreToolUse, PostToolUse, and Stop hooks scoped to the agent's lifecycle"*. However, testing with the `claude-code-voice-hook-agent` confirms that 6 hooks actually fire in agent sessions. The remaining 10 hooks (e.g., Notification, SessionStart, SessionEnd, etc.) do not fire in agent contexts. +> +> **Update (Feb 2026):** The [official hooks reference](https://code.claude.com/docs/en/hooks) now states *"All hook events are supported"* for skill/agent frontmatter hooks. This may mean support has expanded beyond the 6 hooks originally tested. Re-testing recommended to verify if additional hooks now fire in agent sessions. + +### Agent Sound Folders + +Agent-specific sounds are stored in separate folders: +- `.claude/hooks/sounds/agent_pretooluse/` +- `.claude/hooks/sounds/agent_posttooluse/` +- `.claude/hooks/sounds/agent_permissionrequest/` +- `.claude/hooks/sounds/agent_posttoolusefailure/` +- `.claude/hooks/sounds/agent_stop/` +- `.claude/hooks/sounds/agent_subagentstop/` + +### Creating an Agent with Hooks + +1. Create an agent definition file in `.claude/agents/`: + +```markdown +--- +name: my-agent +description: Description of what this agent does +hooks: + PreToolUse: + - type: command + command: python3 ${CLAUDE_PROJECT_DIR}/.claude/hooks/scripts/hooks.py --agent=my-agent + timeout: 5000 + async: true + statusMessage: PreToolUse + PostToolUse: + - type: command + command: python3 ${CLAUDE_PROJECT_DIR}/.claude/hooks/scripts/hooks.py --agent=my-agent + timeout: 5000 + async: true + statusMessage: PostToolUse + PermissionRequest: + - type: command + command: python3 ${CLAUDE_PROJECT_DIR}/.claude/hooks/scripts/hooks.py --agent=my-agent + timeout: 5000 + async: true + statusMessage: PermissionRequest + PostToolUseFailure: + - type: command + command: python3 ${CLAUDE_PROJECT_DIR}/.claude/hooks/scripts/hooks.py --agent=my-agent + timeout: 5000 + async: true + statusMessage: PostToolUseFailure + Stop: + - type: command + command: python3 ${CLAUDE_PROJECT_DIR}/.claude/hooks/scripts/hooks.py --agent=my-agent + timeout: 5000 + async: true + statusMessage: Stop + SubagentStop: + - type: command + command: python3 ${CLAUDE_PROJECT_DIR}/.claude/hooks/scripts/hooks.py --agent=my-agent + timeout: 5000 + async: true + statusMessage: SubagentStop +--- + +Your agent instructions here... +``` + +2. Add sound files to the agent sound folders: + - `agent_pretooluse/agent_pretooluse.wav` + - `agent_posttooluse/agent_posttooluse.wav` + - `agent_permissionrequest/agent_permissionrequest.wav` + - `agent_posttoolusefailure/agent_posttoolusefailure.wav` + - `agent_stop/agent_stop.wav` + - `agent_subagentstop/agent_subagentstop.wav` + +### Example: Weather Fetcher Agent + +See `.claude/agents/claude-code-voice-hook-agent.md` for a complete example of an agent with hooks configured. + +### Hook Option: `once: true` + +The `once: true` option ensures a hook only runs once per session: + +```json +{ + "type": "command", + "command": "python3 .claude/hooks/scripts/hooks.py", + "timeout": 5000, + "once": true +} +``` + +This is useful for hooks like `SessionStart`, `SessionEnd`, and `PreCompact` that should only trigger once. + +> **Note:** The `once` option is for **skills only, not agents**. It works in settings-based hooks and skill frontmatter, but is not supported in agent frontmatter hooks. + +### Hook Option: `async: true` + +Hooks can run in the background without blocking Claude Code's execution by adding `"async": true`: + +```json +{ + "type": "command", + "command": "python3 .claude/hooks/scripts/hooks.py", + "timeout": 5000, + "async": true +} +``` + +**When to use async hooks:** +- Logging and analytics +- Notifications and sound effects +- Any side-effect that shouldn't slow down Claude Code + +This project uses `async: true` for all hooks since voice notifications are side-effects that don't need to block execution. The `timeout` specifies how long the async hook can run before being terminated. + +### Hook Option: `statusMessage` + +The `statusMessage` field sets a custom spinner message displayed to the user while the hook is running: + +```json +{ + "type": "command", + "command": "python3 .claude/hooks/scripts/hooks.py", + "timeout": 5000, + "async": true, + "statusMessage": "PreToolUse" +} +``` + +This project sets `statusMessage` to the hook event name on all hooks, so the spinner briefly shows which hook is firing (e.g., "PreToolUse", "SessionStart", "Stop"). This is most visible for synchronous hooks; for async hooks the message flashes briefly before the hook runs in the background. + +## Hook Types + +Claude Code supports three hook handler types. This project uses `command` hooks for all sound playback. + +### `type: "command"` (used by this project) + +Runs a shell command. Receives JSON input via stdin, communicates results through exit codes and stdout. + +```json +{ + "type": "command", + "command": "python3 .claude/hooks/scripts/hooks.py", + "timeout": 5000, + "async": true +} +``` + +### `type: "prompt"` + +Sends a prompt to a Claude model for single-turn evaluation. The model returns a yes/no decision as JSON (`{"ok": true/false, "reason": "..."}`). Useful for decisions that require judgment rather than deterministic rules. + +```json +{ + "type": "prompt", + "prompt": "Check if all tasks are complete. $ARGUMENTS", + "timeout": 30 +} +``` + +**Supported events:** PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest, UserPromptSubmit, Stop, SubagentStop, TaskCompleted. **Command-only events (not supported for prompt/agent types):** ConfigChange, Notification, PreCompact, SessionEnd, SessionStart, SubagentStart, TeammateIdle, WorktreeCreate, WorktreeRemove. + +### `type: "agent"` + +Spawns a subagent with multi-turn tool access (Read, Grep, Glob) to verify conditions before returning a decision. Same response format as prompt hooks. Useful when verification requires inspecting actual files or test output. + +```json +{ + "type": "agent", + "prompt": "Verify that all unit tests pass. $ARGUMENTS", + "timeout": 120 +} +``` + +## Environment Variables + +Claude Code provides these environment variables to hook scripts: + +| Variable | Availability | Description | +|----------|-------------|-------------| +| `$CLAUDE_PROJECT_DIR` | All hooks | Project root directory. Wrap in quotes for paths with spaces | +| `$CLAUDE_ENV_FILE` | SessionStart only | File path for persisting environment variables for subsequent Bash commands. Use append (`>>`) to preserve variables from other hooks | +| `${CLAUDE_PLUGIN_ROOT}` | Plugin hooks | Plugin's root directory, for scripts bundled with a plugin | +| `$CLAUDE_CODE_REMOTE` | All hooks | Set to `"true"` in remote web environments, not set in local CLI | +| `session_id` (via stdin JSON) | All hooks | Current session ID, received as part of the JSON input on stdin (not an environment variable) | + +### Common Input Fields (stdin JSON) + +Every hook receives a JSON object on stdin containing these common fields, in addition to any hook-specific fields listed in the Options column above: + +| Field | Type | Description | +|-------|------|-------------| +| `hook_event_name` | string | Name of the hook event that fired (e.g., `"PreToolUse"`, `"Stop"`) | +| `session_id` | string | Current session identifier | +| `transcript_path` | string | Path to the conversation transcript JSON file | +| `cwd` | string | Current working directory | +| `permission_mode` | string | Current permission mode: `default`, `plan`, `acceptEdits`, `dontAsk`, or `bypassPermissions` | + +> **Note:** Hook-specific fields (e.g., `tool_name` for PreToolUse, `last_assistant_message` for Stop) are listed in the Options column of the [Hook Events Overview](#hook-events-overview---official-18-hooks) table above. + +## Hooks Management Commands + +Claude Code provides built-in commands for managing hooks: + +- **`/hooks`** — Interactive hook management UI. View, add, and delete hooks without editing JSON files. Hooks are labeled by source: `[User]`, `[Project]`, `[Local]`, `[Plugin]`. You can also toggle `disableAllHooks` from this menu. +- **`claude hooks reload`** — Reload hooks configuration without restarting the session. Useful after editing settings files (since v2.0.47). + +## MCP Tool Matchers + +For `PreToolUse`, `PostToolUse`, and `PermissionRequest` hooks, you can match MCP (Model Context Protocol) tools using the pattern `mcp____`: + +```json +{ + "hooks": { + "PreToolUse": [{ + "matcher": "mcp__memory__.*", + "hooks": [{ "type": "command", "command": "echo 'MCP memory tool used'" }] + }] + } +} +``` + +Full regex is supported: `mcp__memory__.*` (all tools from memory server), `mcp__.*__write.*` (any write tool from any server). + +### Per-Hook Matcher Reference + +Matchers filter which events trigger a hook. Not all hooks support matchers — hooks without matcher support always fire. + +| Hook | Matcher Field | Possible Values | Example | +|------|--------------|-----------------|---------| +| `PreToolUse` | `tool_name` | Any tool name: `Bash`, `Read`, `Edit`, `Write`, `Glob`, `Grep`, `mcp__*` | `"matcher": "Bash"` | +| `PermissionRequest` | `tool_name` | Same as PreToolUse | `"matcher": "mcp__memory__.*"` | +| `PostToolUse` | `tool_name` | Same as PreToolUse | `"matcher": "Write"` | +| `PostToolUseFailure` | `tool_name` | Same as PreToolUse | `"matcher": "Bash"` | +| `Notification` | `notification_type` | `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog` | `"matcher": "permission_prompt"` | +| `SubagentStart` | `agent_type` | `Bash`, `Explore`, `Plan`, or custom agent name | `"matcher": "Bash"` | +| `SubagentStop` | `agent_type` | `Bash`, `Explore`, `Plan`, or custom agent name | `"matcher": "Bash"` | +| `SessionStart` | `source` | `startup`, `resume`, `clear`, `compact` | `"matcher": "startup"` | +| `SessionEnd` | `reason` | `clear`, `logout`, `prompt_input_exit`, `bypass_permissions_disabled`, `other` | `"matcher": "logout"` | +| `PreCompact` | `trigger` | `manual`, `auto` | `"matcher": "auto"` | +| `ConfigChange` | `source` | `user_settings`, `project_settings`, `local_settings`, `policy_settings`, `skills` | `"matcher": "project_settings"` | +| `UserPromptSubmit` | — | No matcher support | Always fires | +| `Stop` | — | No matcher support | Always fires | +| `TeammateIdle` | — | No matcher support | Always fires | +| `TaskCompleted` | — | No matcher support | Always fires | +| `WorktreeCreate` | — | No matcher support | Always fires | +| `WorktreeRemove` | — | No matcher support | Always fires | +| `Setup` | — | No matcher support | Always fires | + +## Known Issues & Workarounds + +### Agent Stop Hook Bug (SubagentStop vs Stop) + +**Bug Report:** [GitHub Issue #19220](https://github.com/anthropics/claude-code/issues/19220) + +**Issue:** When defining a `Stop` hook in an agent's frontmatter, the `hook_event_name` passed to the hook script is `"SubagentStop"` instead of `"Stop"`. This contradicts the official documentation and breaks consistency with other agent hooks (`PreToolUse` and `PostToolUse`), which correctly pass their configured names. + +| Hook | Defined As | Received As | Status | +|------|------------|-------------|--------| +| PreToolUse | `PreToolUse:` | `"PreToolUse"` | ✅ Correct | +| PostToolUse | `PostToolUse:` | `"PostToolUse"` | ✅ Correct | +| Stop | `Stop:` | `"SubagentStop"` | ❌ Inconsistent | + +**Status:** The [official hooks reference](https://code.claude.com/docs/en/hooks#hooks-in-skills-and-agents) now documents this as expected behavior: *"For subagents, Stop hooks are automatically converted to SubagentStop since that is the event that fires when a subagent completes."* This project handles it via the `AGENT_HOOK_SOUND_MAP` in `hooks.py`, which has a separate `SubagentStop` entry that maps to the `agent_subagentstop` sound folder. + +### PreToolUse Decision Control Deprecation + +The `PreToolUse` hook previously used top-level `decision` and `reason` fields for blocking tool calls. These are now **deprecated**. Use `hookSpecificOutput.permissionDecision` and `hookSpecificOutput.permissionDecisionReason` instead: + +| Deprecated | Current | +|-----------|---------| +| `"decision": "approve"` | `"hookSpecificOutput": { "permissionDecision": "allow" }` | +| `"decision": "block"` | `"hookSpecificOutput": { "permissionDecision": "deny" }` | + +This does not affect this project since `hooks.py` uses async sound playback and does not use decision control. + +## Decision Control Patterns + +Different hooks use different output schemas for blocking or controlling execution. This project does not use decision control (all hooks are async sound playback), but for reference: + +| Hook(s) | Control Method | Values | +|---------|---------------|--------| +| PreToolUse | `hookSpecificOutput.permissionDecision` | `allow`, `deny`, `ask` | +| PreToolUse | `hookSpecificOutput.autoAllow` | `true` — auto-approve future uses of this tool (since v2.0.76) | +| PermissionRequest | `hookSpecificOutput.decision.behavior` | `allow`, `deny` | +| PostToolUse, Stop, SubagentStop, ConfigChange | Top-level `decision` | `block` | +| TeammateIdle, TaskCompleted | Exit code 2 only | No JSON decision control | +| UserPromptSubmit | Can modify `prompt` field | Returns modified prompt via stdout | +| WorktreeCreate | Non-zero exit + stdout path | Non-zero exit fails creation; stdout provides worktree path | + +### Universal JSON Output Fields + +All hooks can return these fields via stdout JSON: + +| Field | Type | Description | +|-------|------|-------------| +| `continue` | bool | If `false`, stops Claude entirely | +| `stopReason` | string | Message shown when `continue` is false | +| `suppressOutput` | bool | Hides stdout from verbose mode | +| `systemMessage` | string | Warning message shown to user | +| `additionalContext` | string | Context added to Claude's conversation | + +## Hook Deduplication & External Changes + +- **Hook deduplication:** Identical hook handlers defined in multiple settings locations run only once in parallel, preventing duplicate execution. +- **External change detection:** Claude Code warns when hooks are modified externally (e.g., by another process editing settings files) during an active session. \ No newline at end of file diff --git a/.claude/hooks/config/hooks-config.json b/.claude/hooks/config/hooks-config.json index c3b5655..8c61ee2 100644 --- a/.claude/hooks/config/hooks-config.json +++ b/.claude/hooks/config/hooks-config.json @@ -15,5 +15,7 @@ "disableTeammateIdleHook": false, "disableTaskCompletedHook": false, "disableConfigChangeHook": false, + "disableWorktreeCreateHook": false, + "disableWorktreeRemoveHook": false, "disableLogging": true } diff --git a/.claude/hooks/scripts/hooks.py b/.claude/hooks/scripts/hooks.py index e4dcff2..a810eff 100644 --- a/.claude/hooks/scripts/hooks.py +++ b/.claude/hooks/scripts/hooks.py @@ -3,9 +3,13 @@ Claude Code Hook Handler ============================================= This script handles events from Claude Code and plays sounds for different hook events. -Supports all 16 Claude Code hook event names: https://docs.claude.com/en/docs/claude-code/hooks-guide +Supports all 18 Claude Code hooks: https://code.claude.com/docs/en/hooks Special handling for git commits: plays pretooluse-git-committing.mp3 + +Agent Support: + Use --agent= to play agent-specific sounds from agent_* folders. + Agent frontmatter hooks support 6 hooks: PreToolUse, PostToolUse, PermissionRequest, PostToolUseFailure, Stop, SubagentStop """ import sys @@ -13,6 +17,7 @@ import json import subprocess import re import platform +import argparse from pathlib import Path # Windows-only module for playing WAV files @@ -24,22 +29,36 @@ except ImportError: # ===== HOOK EVENT TO SOUND FOLDER MAPPING ===== # Maps each hook event to its corresponding sound folder HOOK_SOUND_MAP = { - "SessionStart": "sessionstart", - "SessionEnd": "sessionend", - "UserPromptSubmit": "userpromptsubmit", "PreToolUse": "pretooluse", + "PermissionRequest": "permissionrequest", "PostToolUse": "posttooluse", "PostToolUseFailure": "posttoolusefailure", - "PermissionRequest": "permissionrequest", + "UserPromptSubmit": "userpromptsubmit", "Notification": "notification", "Stop": "stop", "SubagentStart": "subagentstart", "SubagentStop": "subagentstop", "PreCompact": "precompact", + "SessionStart": "sessionstart", + "SessionEnd": "sessionend", "Setup": "setup", "TeammateIdle": "teammateidle", "TaskCompleted": "taskcompleted", "ConfigChange": "configchange", + "WorktreeCreate": "worktreecreate", + "WorktreeRemove": "worktreeremove" +} + +# ===== AGENT HOOK EVENT TO SOUND FOLDER MAPPING ===== +# Maps agent hook events to agent-specific sound folders +# Only the 6 hooks that actually fire in agent contexts are mapped +AGENT_HOOK_SOUND_MAP = { + "PreToolUse": "agent_pretooluse", + "PostToolUse": "agent_posttooluse", + "PermissionRequest": "agent_permissionrequest", + "PostToolUseFailure": "agent_posttoolusefailure", + "Stop": "agent_stop", + "SubagentStop": "agent_subagentstop" } # ===== BASH COMMAND PATTERNS ===== @@ -232,11 +251,68 @@ def is_hook_disabled(event_name): print(f"Error in is_hook_disabled: {e}", file=sys.stderr) return False -def log_hook_data(hook_data): +def is_logging_disabled(): + """ + Check if logging is disabled in the config files. + Uses fallback logic: hooks-config.local.json -> hooks-config.json + + Returns: + True if logging is disabled, False otherwise + """ + try: + # Scripts are in .claude/hooks/scripts/, config is in .claude/hooks/config/ + script_dir = Path(__file__).parent # .claude/hooks/scripts/ + hooks_dir = script_dir.parent # .claude/hooks/ + config_dir = hooks_dir / "config" # .claude/hooks/config/ + + local_config_path = config_dir / "hooks-config.local.json" + default_config_path = config_dir / "hooks-config.json" + + # Try to load local config first + local_config = None + if local_config_path.exists(): + try: + with open(local_config_path, "r", encoding="utf-8") as config_file: + local_config = json.load(config_file) + except Exception as e: + print(f"Error reading local config: {e}", file=sys.stderr) + + # Try to load default config + default_config = None + if default_config_path.exists(): + try: + with open(default_config_path, "r", encoding="utf-8") as config_file: + default_config = json.load(config_file) + except Exception as e: + print(f"Error reading default config: {e}", file=sys.stderr) + + # Apply fallback logic: local -> default -> False (logging enabled) + if local_config is not None and "disableLogging" in local_config: + return local_config["disableLogging"] + elif default_config is not None and "disableLogging" in default_config: + return default_config["disableLogging"] + else: + # If neither config has the key, assume logging is enabled + return False + + except Exception as e: + # If anything goes wrong, assume logging is enabled + print(f"Error in is_logging_disabled: {e}", file=sys.stderr) + return False + +def log_hook_data(hook_data, agent_name=None): """ Log the full hook_data to hooks-log.jsonl for debugging/auditing. Log file is stored at .claude/hooks/logs/hooks-log.jsonl + + Args: + hook_data: Dictionary containing event information from Claude + agent_name: Optional agent name if hook was invoked from a sub-agent """ + # Check if logging is disabled + if is_logging_disabled(): + return + try: # Scripts are in .claude/hooks/scripts/, logs are in .claude/hooks/logs/ script_dir = Path(__file__).parent # .claude/hooks/scripts/ @@ -246,9 +322,20 @@ def log_hook_data(hook_data): # Ensure logs directory exists logs_dir.mkdir(parents=True, exist_ok=True) + # Add source field to indicate if hook was called from main session or sub-agent + log_entry = hook_data.copy() + + # Remove fields we don't need in logs + log_entry.pop("transcript_path", None) + log_entry.pop("cwd", None) + + # Only add agent name if hook was invoked from a sub-agent + if agent_name: + log_entry["invoked_by_agent"] = agent_name + log_path = logs_dir / "hooks-log.jsonl" with open(log_path, "a", encoding="utf-8") as log_file: - log_file.write(json.dumps(hook_data, ensure_ascii=False, indent=2) + "\n") + log_file.write(json.dumps(log_entry, ensure_ascii=False, indent=2) + "\n") except Exception as e: # Fail silently, but print to stderr for visibility print(f"Failed to log hook_data: {e}", file=sys.stderr) @@ -274,12 +361,13 @@ def detect_bash_command_sound(command): return None -def get_sound_name(hook_data): +def get_sound_name(hook_data, agent_name=None): """ Determine which sound to play based on the hook event and context. Args: hook_data: Dictionary containing event information from Claude + agent_name: Optional agent name for agent-specific sounds Returns: Sound name (string) or None if no sound should play @@ -287,6 +375,10 @@ def get_sound_name(hook_data): event_name = hook_data.get("hook_event_name", "") tool_name = hook_data.get("tool_name", "") + # If this is an agent hook, use agent-specific sounds + if agent_name: + return AGENT_HOOK_SOUND_MAP.get(event_name) + # Check if this is a PreToolUse event with Bash tool if event_name == "PreToolUse" and tool_name == "Bash": tool_input = hook_data.get("tool_input", {}) @@ -300,20 +392,43 @@ def get_sound_name(hook_data): # Return the default sound for this hook event return HOOK_SOUND_MAP.get(event_name) +def parse_arguments(): + """ + Parse command line arguments. + + Returns: + Parsed arguments namespace + """ + parser = argparse.ArgumentParser( + description="Claude Code Hook Handler - plays sounds for hook events" + ) + parser.add_argument( + "--agent", + type=str, + default=None, + help="Agent name for agent-specific sounds (used by agent frontmatter hooks)" + ) + return parser.parse_args() + + def main(): """ Main program - this runs when Claude triggers a hook. How it works: - 1. Claude sends event data as JSON through stdin - 2. We check if this specific hook is disabled in hooks-config.json - 3. We parse the JSON to understand which hook event occurred - 4. We check for special bash commands (like git commit) - 5. We play the corresponding sound for that event - 6. We exit successfully + 1. Parse command line arguments (--agent for agent-specific sounds) + 2. Claude sends event data as JSON through stdin + 3. We check if this specific hook is disabled in hooks-config.json + 4. We parse the JSON to understand which hook event occurred + 5. We check for special bash commands (like git commit) + 6. We play the corresponding sound for that event + 7. We exit successfully """ try: - # Step 1: Read the event data from Claude + # Step 1: Parse command line arguments + args = parse_arguments() + + # Step 2: Read the event data from Claude stdin_content = sys.stdin.read().strip() # If stdin is empty, exit gracefully (hook was called without data) @@ -321,22 +436,24 @@ def main(): sys.exit(0) input_data = json.loads(stdin_content) - log_hook_data(input_data) - # Step 2: Check if this hook is disabled + # Log hook data with source information (main session vs sub-agent) + log_hook_data(input_data, agent_name=args.agent) + + # Step 3: Check if this hook is disabled (skip for agent hooks) event_name = input_data.get("hook_event_name", "") - if is_hook_disabled(event_name): + if not args.agent and is_hook_disabled(event_name): # Hook is disabled, exit silently without playing sound sys.exit(0) - # Step 3: Determine which sound to play (may be special or default) - sound_name = get_sound_name(input_data) + # Step 4: Determine which sound to play (may be special, default, or agent-specific) + sound_name = get_sound_name(input_data, agent_name=args.agent) - # Step 4: Play the sound (if we found one) + # Step 5: Play the sound (if we found one) if sound_name: play_sound(sound_name) - # Step 5: Exit successfully + # Step 6: Exit successfully # Always exit with code 0 so we don't interrupt Claude's work sys.exit(0) @@ -352,4 +469,4 @@ def main(): # Entry point - Python calls main() when the script runs if __name__ == "__main__": - main() + main() \ No newline at end of file diff --git a/.claude/hooks/scripts/run-hooks-py-os-wise.sh b/.claude/hooks/scripts/run-hooks-py-os-wise.sh deleted file mode 100755 index d4a970d..0000000 --- a/.claude/hooks/scripts/run-hooks-py-os-wise.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash -# Cross-platform Python wrapper for Claude Code hooks -# Automatically selects python or python3 based on OS and availability - -# Get the directory where this script is located -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -PYTHON_SCRIPT="$SCRIPT_DIR/hooks.py" - -# Function to detect and use the correct Python command -run_python() { - # Check if python3 is available (preferred on Unix-like systems) - if command -v python3 &> /dev/null; then - python3 "$PYTHON_SCRIPT" - # Fallback to python command (Windows and some systems) - elif command -v python &> /dev/null; then - python "$PYTHON_SCRIPT" - else - echo "Error: Neither python nor python3 command found" >&2 - exit 1 - fi -} - -# Run the Python script -run_python diff --git a/.claude/hooks/sounds/agent_permissionrequest/agent_permissionrequest.mp3 b/.claude/hooks/sounds/agent_permissionrequest/agent_permissionrequest.mp3 new file mode 100644 index 0000000..7384149 Binary files /dev/null and b/.claude/hooks/sounds/agent_permissionrequest/agent_permissionrequest.mp3 differ diff --git a/.claude/hooks/sounds/agent_permissionrequest/agent_permissionrequest.wav b/.claude/hooks/sounds/agent_permissionrequest/agent_permissionrequest.wav new file mode 100644 index 0000000..26e351d Binary files /dev/null and b/.claude/hooks/sounds/agent_permissionrequest/agent_permissionrequest.wav differ diff --git a/.claude/hooks/sounds/agent_posttooluse/agent_posttooluse.mp3 b/.claude/hooks/sounds/agent_posttooluse/agent_posttooluse.mp3 new file mode 100644 index 0000000..ff81c90 Binary files /dev/null and b/.claude/hooks/sounds/agent_posttooluse/agent_posttooluse.mp3 differ diff --git a/.claude/hooks/sounds/agent_posttooluse/agent_posttooluse.wav b/.claude/hooks/sounds/agent_posttooluse/agent_posttooluse.wav new file mode 100644 index 0000000..1ed6dcf Binary files /dev/null and b/.claude/hooks/sounds/agent_posttooluse/agent_posttooluse.wav differ diff --git a/.claude/hooks/sounds/agent_posttoolusefailure/agent_posttoolusefailure.mp3 b/.claude/hooks/sounds/agent_posttoolusefailure/agent_posttoolusefailure.mp3 new file mode 100644 index 0000000..8a0312d Binary files /dev/null and b/.claude/hooks/sounds/agent_posttoolusefailure/agent_posttoolusefailure.mp3 differ diff --git a/.claude/hooks/sounds/agent_posttoolusefailure/agent_posttoolusefailure.wav b/.claude/hooks/sounds/agent_posttoolusefailure/agent_posttoolusefailure.wav new file mode 100644 index 0000000..98974ce Binary files /dev/null and b/.claude/hooks/sounds/agent_posttoolusefailure/agent_posttoolusefailure.wav differ diff --git a/.claude/hooks/sounds/agent_pretooluse/agent_pretooluse.mp3 b/.claude/hooks/sounds/agent_pretooluse/agent_pretooluse.mp3 new file mode 100644 index 0000000..a83a2fe Binary files /dev/null and b/.claude/hooks/sounds/agent_pretooluse/agent_pretooluse.mp3 differ diff --git a/.claude/hooks/sounds/agent_pretooluse/agent_pretooluse.wav b/.claude/hooks/sounds/agent_pretooluse/agent_pretooluse.wav new file mode 100644 index 0000000..ab8f4e9 Binary files /dev/null and b/.claude/hooks/sounds/agent_pretooluse/agent_pretooluse.wav differ diff --git a/.claude/hooks/sounds/agent_stop/agent_stop.mp3 b/.claude/hooks/sounds/agent_stop/agent_stop.mp3 new file mode 100644 index 0000000..7dfb52d Binary files /dev/null and b/.claude/hooks/sounds/agent_stop/agent_stop.mp3 differ diff --git a/.claude/hooks/sounds/agent_stop/agent_stop.wav b/.claude/hooks/sounds/agent_stop/agent_stop.wav new file mode 100644 index 0000000..e3e0a09 Binary files /dev/null and b/.claude/hooks/sounds/agent_stop/agent_stop.wav differ diff --git a/.claude/hooks/sounds/agent_subagentstop/agent_subagentstop.mp3 b/.claude/hooks/sounds/agent_subagentstop/agent_subagentstop.mp3 new file mode 100644 index 0000000..9fb8282 Binary files /dev/null and b/.claude/hooks/sounds/agent_subagentstop/agent_subagentstop.mp3 differ diff --git a/.claude/hooks/sounds/agent_subagentstop/agent_subagentstop.wav b/.claude/hooks/sounds/agent_subagentstop/agent_subagentstop.wav new file mode 100644 index 0000000..865f92a Binary files /dev/null and b/.claude/hooks/sounds/agent_subagentstop/agent_subagentstop.wav differ diff --git a/.claude/hooks/sounds/configchange/configchange.mp3 b/.claude/hooks/sounds/configchange/configchange.mp3 new file mode 100644 index 0000000..41e89e2 Binary files /dev/null and b/.claude/hooks/sounds/configchange/configchange.mp3 differ diff --git a/.claude/hooks/sounds/configchange/configchange.wav b/.claude/hooks/sounds/configchange/configchange.wav new file mode 100644 index 0000000..3a84e8a Binary files /dev/null and b/.claude/hooks/sounds/configchange/configchange.wav differ diff --git a/.claude/hooks/sounds/permissionrequest/permissionrequest.mp3 b/.claude/hooks/sounds/permissionrequest/permissionrequest.mp3 new file mode 100644 index 0000000..ca6cb8e Binary files /dev/null and b/.claude/hooks/sounds/permissionrequest/permissionrequest.mp3 differ diff --git a/.claude/hooks/sounds/permissionrequest/permissionrequest.wav b/.claude/hooks/sounds/permissionrequest/permissionrequest.wav new file mode 100644 index 0000000..589cd40 Binary files /dev/null and b/.claude/hooks/sounds/permissionrequest/permissionrequest.wav differ diff --git a/.claude/hooks/sounds/posttooluse/posttooluse.mp3 b/.claude/hooks/sounds/posttooluse/posttooluse.mp3 index 07c225b..7112855 100644 Binary files a/.claude/hooks/sounds/posttooluse/posttooluse.mp3 and b/.claude/hooks/sounds/posttooluse/posttooluse.mp3 differ diff --git a/.claude/hooks/sounds/posttooluse/posttooluse.wav b/.claude/hooks/sounds/posttooluse/posttooluse.wav index 7fc103d..c3ad5f6 100644 Binary files a/.claude/hooks/sounds/posttooluse/posttooluse.wav and b/.claude/hooks/sounds/posttooluse/posttooluse.wav differ diff --git a/.claude/hooks/sounds/posttoolusefailure/posttoolusefailure.mp3 b/.claude/hooks/sounds/posttoolusefailure/posttoolusefailure.mp3 new file mode 100644 index 0000000..416103b Binary files /dev/null and b/.claude/hooks/sounds/posttoolusefailure/posttoolusefailure.mp3 differ diff --git a/.claude/hooks/sounds/posttoolusefailure/posttoolusefailure.wav b/.claude/hooks/sounds/posttoolusefailure/posttoolusefailure.wav new file mode 100644 index 0000000..ab7c038 Binary files /dev/null and b/.claude/hooks/sounds/posttoolusefailure/posttoolusefailure.wav differ diff --git a/.claude/hooks/sounds/setup/Setup.mp3 b/.claude/hooks/sounds/setup/Setup.mp3 new file mode 100644 index 0000000..da18f6c Binary files /dev/null and b/.claude/hooks/sounds/setup/Setup.mp3 differ diff --git a/.claude/hooks/sounds/setup/Setup.wav b/.claude/hooks/sounds/setup/Setup.wav new file mode 100644 index 0000000..da1f9e7 Binary files /dev/null and b/.claude/hooks/sounds/setup/Setup.wav differ diff --git a/.claude/hooks/sounds/subagentstart/subagentstart.mp3 b/.claude/hooks/sounds/subagentstart/subagentstart.mp3 new file mode 100644 index 0000000..447cb78 Binary files /dev/null and b/.claude/hooks/sounds/subagentstart/subagentstart.mp3 differ diff --git a/.claude/hooks/sounds/subagentstart/subagentstart.wav b/.claude/hooks/sounds/subagentstart/subagentstart.wav new file mode 100644 index 0000000..83780a0 Binary files /dev/null and b/.claude/hooks/sounds/subagentstart/subagentstart.wav differ diff --git a/.claude/hooks/sounds/taskcompleted/taskcompleted.mp3 b/.claude/hooks/sounds/taskcompleted/taskcompleted.mp3 new file mode 100644 index 0000000..54b498e Binary files /dev/null and b/.claude/hooks/sounds/taskcompleted/taskcompleted.mp3 differ diff --git a/.claude/hooks/sounds/taskcompleted/taskcompleted.wav b/.claude/hooks/sounds/taskcompleted/taskcompleted.wav new file mode 100644 index 0000000..9b3198a Binary files /dev/null and b/.claude/hooks/sounds/taskcompleted/taskcompleted.wav differ diff --git a/.claude/hooks/sounds/teammateidle/teammateidle.mp3 b/.claude/hooks/sounds/teammateidle/teammateidle.mp3 new file mode 100644 index 0000000..53998ec Binary files /dev/null and b/.claude/hooks/sounds/teammateidle/teammateidle.mp3 differ diff --git a/.claude/hooks/sounds/teammateidle/teammateidle.wav b/.claude/hooks/sounds/teammateidle/teammateidle.wav new file mode 100644 index 0000000..726d9b9 Binary files /dev/null and b/.claude/hooks/sounds/teammateidle/teammateidle.wav differ diff --git a/.claude/hooks/sounds/worktreecreate/worktreecreate.mp3 b/.claude/hooks/sounds/worktreecreate/worktreecreate.mp3 new file mode 100644 index 0000000..2ab6dd1 Binary files /dev/null and b/.claude/hooks/sounds/worktreecreate/worktreecreate.mp3 differ diff --git a/.claude/hooks/sounds/worktreecreate/worktreecreate.wav b/.claude/hooks/sounds/worktreecreate/worktreecreate.wav new file mode 100644 index 0000000..04d3208 Binary files /dev/null and b/.claude/hooks/sounds/worktreecreate/worktreecreate.wav differ diff --git a/.claude/hooks/sounds/worktreeremove/worktreeremove.mp3 b/.claude/hooks/sounds/worktreeremove/worktreeremove.mp3 new file mode 100644 index 0000000..87b70e3 Binary files /dev/null and b/.claude/hooks/sounds/worktreeremove/worktreeremove.mp3 differ diff --git a/.claude/hooks/sounds/worktreeremove/worktreeremove.wav b/.claude/hooks/sounds/worktreeremove/worktreeremove.wav new file mode 100644 index 0000000..061b36d Binary files /dev/null and b/.claude/hooks/sounds/worktreeremove/worktreeremove.wav differ diff --git a/.claude/settings.json b/.claude/settings.json index c589e3f..e21046f 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -67,7 +67,9 @@ { "type": "command", "command": "python3 ${CLAUDE_PROJECT_DIR}/.claude/hooks/scripts/hooks.py", - "timeout": 5000 + "timeout": 5000, + "async": true, + "statusMessage": "PreToolUse" } ] } @@ -78,7 +80,9 @@ { "type": "command", "command": "python3 ${CLAUDE_PROJECT_DIR}/.claude/hooks/scripts/hooks.py", - "timeout": 5000 + "timeout": 5000, + "async": true, + "statusMessage": "PermissionRequest" } ] } @@ -89,7 +93,22 @@ { "type": "command", "command": "python3 ${CLAUDE_PROJECT_DIR}/.claude/hooks/scripts/hooks.py", - "timeout": 5000 + "timeout": 5000, + "async": true, + "statusMessage": "PostToolUse" + } + ] + } + ], + "PostToolUseFailure": [ + { + "hooks": [ + { + "type": "command", + "command": "python3 ${CLAUDE_PROJECT_DIR}/.claude/hooks/scripts/hooks.py", + "timeout": 5000, + "async": true, + "statusMessage": "PostToolUseFailure" } ] } @@ -100,7 +119,9 @@ { "type": "command", "command": "python3 ${CLAUDE_PROJECT_DIR}/.claude/hooks/scripts/hooks.py", - "timeout": 5000 + "timeout": 5000, + "async": true, + "statusMessage": "UserPromptSubmit" } ] } @@ -111,7 +132,9 @@ { "type": "command", "command": "python3 ${CLAUDE_PROJECT_DIR}/.claude/hooks/scripts/hooks.py", - "timeout": 5000 + "timeout": 5000, + "async": true, + "statusMessage": "Notification" } ] } @@ -122,7 +145,9 @@ { "type": "command", "command": "python3 ${CLAUDE_PROJECT_DIR}/.claude/hooks/scripts/hooks.py", - "timeout": 5000 + "timeout": 5000, + "async": true, + "statusMessage": "Stop" } ] } @@ -133,7 +158,9 @@ { "type": "command", "command": "python3 ${CLAUDE_PROJECT_DIR}/.claude/hooks/scripts/hooks.py", - "timeout": 5000 + "timeout": 5000, + "async": true, + "statusMessage": "SubagentStart" } ] } @@ -144,7 +171,9 @@ { "type": "command", "command": "python3 ${CLAUDE_PROJECT_DIR}/.claude/hooks/scripts/hooks.py", - "timeout": 5000 + "timeout": 5000, + "async": true, + "statusMessage": "SubagentStop" } ] } @@ -156,7 +185,9 @@ "type": "command", "command": "python3 ${CLAUDE_PROJECT_DIR}/.claude/hooks/scripts/hooks.py", "timeout": 5000, - "once": true + "async": true, + "once": true, + "statusMessage": "PreCompact" } ] } @@ -168,7 +199,9 @@ "type": "command", "command": "python3 ${CLAUDE_PROJECT_DIR}/.claude/hooks/scripts/hooks.py", "timeout": 5000, - "once": true + "async": true, + "once": true, + "statusMessage": "SessionStart" } ] } @@ -180,7 +213,9 @@ "type": "command", "command": "python3 ${CLAUDE_PROJECT_DIR}/.claude/hooks/scripts/hooks.py", "timeout": 5000, - "once": true + "async": true, + "once": true, + "statusMessage": "SessionEnd" } ] } @@ -191,7 +226,74 @@ { "type": "command", "command": "python3 ${CLAUDE_PROJECT_DIR}/.claude/hooks/scripts/hooks.py", - "timeout": 30000 + "timeout": 30000, + "async": true, + "statusMessage": "Setup" + } + ] + } + ], + "TeammateIdle": [ + { + "hooks": [ + { + "type": "command", + "command": "python3 ${CLAUDE_PROJECT_DIR}/.claude/hooks/scripts/hooks.py", + "timeout": 5000, + "async": true, + "statusMessage": "TeammateIdle" + } + ] + } + ], + "TaskCompleted": [ + { + "hooks": [ + { + "type": "command", + "command": "python3 ${CLAUDE_PROJECT_DIR}/.claude/hooks/scripts/hooks.py", + "timeout": 5000, + "async": true, + "statusMessage": "TaskCompleted" + } + ] + } + ], + "ConfigChange": [ + { + "hooks": [ + { + "type": "command", + "command": "python3 ${CLAUDE_PROJECT_DIR}/.claude/hooks/scripts/hooks.py", + "timeout": 5000, + "async": true, + "statusMessage": "ConfigChange" + } + ] + } + ], + "WorktreeCreate": [ + { + "hooks": [ + { + "type": "command", + "command": "python3 ${CLAUDE_PROJECT_DIR}/.claude/hooks/scripts/hooks.py", + "timeout": 5000, + "async": true, + "statusMessage": "WorktreeCreate" + } + ] + } + ], + "WorktreeRemove": [ + { + "hooks": [ + { + "type": "command", + "command": "python3 ${CLAUDE_PROJECT_DIR}/.claude/hooks/scripts/hooks.py", + "timeout": 5000, + "async": true, + "statusMessage": "WorktreeRemove" } ] }