claude-code-voice-hooks added
This commit is contained in:
+396
-34
@@ -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__<server>__<tool>`:
|
||||
|
||||
```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.
|
||||
@@ -15,5 +15,7 @@
|
||||
"disableTeammateIdleHook": false,
|
||||
"disableTaskCompletedHook": false,
|
||||
"disableConfigChangeHook": false,
|
||||
"disableWorktreeCreateHook": false,
|
||||
"disableWorktreeRemoveHook": false,
|
||||
"disableLogging": true
|
||||
}
|
||||
|
||||
+140
-23
@@ -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=<name> 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()
|
||||
@@ -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
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+114
-12
@@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user