--- name: hook-creator description: "Create new Claude Code lifecycle hook (PreToolUse/PostToolUse/Stop/SessionStart) with bash + hooks.json. Triggers: create hook, lifecycle hook, PreToolUse, PostToolUse, hook event." effort: high disable-model-invocation: true argument-hint: "[hook event or description]" allowed-tools: Read, Write, Edit, Bash, Grep, Glob --- # Hook Creator $ARGUMENTS Create a new Claude Code hook following ai-toolkit conventions. ## Supported Hook Events ### Core lifecycle | Event | Fires When | Matcher | Typical Use | |-------|-----------|---------|-------------| | `SessionStart` | Session begins, resumes, or clears | `startup\|resume\|clear` | Context injection, rules reminder | | `SessionEnd` | Session is closing | any | Flush logs, save transcripts | | `UserPromptSubmit` | User submits a prompt | any | Prompt governance, usage tracking | | `Notification` | Claude sends a notification | any | OS alerts, Slack pings | | `MessageDisplay` | Assistant message text streams in completed-line batches | none | Return `displayContent` to replace the rendered batch without changing the transcript | ### Tool lifecycle | Event | Fires When | Matcher | Typical Use | |-------|-----------|---------|-------------| | `PreToolUse` | Before a tool executes | tool name (e.g. `Bash`) or `if:` rule | Safety guards, validation, `"defer"` for headless | | `PostToolUse` | After a tool executes | tool name | Feedback loops, logging, format-on-save | | `PostToolUseFailure` | After a tool fails | tool name | Failure telemetry, recovery hints | | `PostToolBatch` | After a batch of tool calls completes | any | Batch summaries, aggregate validation | ### Turn lifecycle | Event | Fires When | Matcher | Typical Use | |-------|-----------|---------|-------------| | `Stop` | Claude finishes responding | any | Quality checks, session save | | `StopFailure` | Turn ends due to an API error (rate limit, auth) | any | Alerting, fallback behavior | | `UserPromptExpansion` | Claude expands or rewrites a submitted prompt | any | Prompt policy and context shaping | ### Subagent lifecycle | Event | Fires When | Matcher | Typical Use | |-------|-----------|---------|-------------| | `SubagentStart` | Subagent launches | any | Observability | | `SubagentStop` | Subagent completes | any | Result validation | ### Compaction | Event | Fires When | Matcher | Typical Use | |-------|-----------|---------|-------------| | `PreCompact` | Before context compaction; can block with exit 2 or `{"decision":"block"}` | any | Context preservation | | `PostCompact` | After compaction completes | any | Re-inject state that was summarized away | ### Model changes | Event | Fires When | Matcher | Typical Use | |-------|-----------|---------|-------------| | `PreModelSwitch` | Before a user/client model switch; exit 2 or `decision: "block"` cancels it | canonical target model | Model policy; requires Claude Code 2.1.251+ | | `PostModelSwitch` | After the session model changes; cannot block | canonical target model | Model-specific context; requires Claude Code 2.1.251+ | ### Permissions & elicitation | Event | Fires When | Matcher | Typical Use | |-------|-----------|---------|-------------| | `PermissionRequest` | Tool awaiting permission; can return `updatedInput` | any | Headless approval flows | | `PermissionDenied` | Auto-mode classifier denied a tool call; return `{retry: true}` to allow retry | any | Coach the model, log denials | | `Elicitation` | MCP `elicitation/create` request arrives | any | Intercept / override MCP UI prompts | | `ElicitationResult` | Elicitation response ready to be sent back | any | Validate / transform elicitation replies | ### Agent Teams | Event | Fires When | Matcher | Typical Use | |-------|-----------|---------|-------------| | `TaskCreated` | New task registered via `TaskCreate` | any | Audit, assignment routing | | `TaskCompleted` | Agent Teams task finished | any | Lint, type check, notify | | `TeammateIdle` | Agent Teams member idle | any | Completeness reminder | ### Worktrees & environment | Event | Fires When | Matcher | Typical Use | |-------|-----------|---------|-------------| | `WorktreeCreate` | Worktree is being created; `type: "http"` can return `hookSpecificOutput.worktreePath` | any | Provision worktree dirs | | `WorktreeRemove` | Worktree is being removed | any | Cleanup | | `CwdChanged` | Working directory changes during a session | any | Reactive env management (e.g., direnv) | | `DirectoryAdded` | A working directory was added with `/add-dir` or SDK `register_repo_root`; runs asynchronously after the add and cannot block it | `slash_command\|register_repo_root` | Prepare a newly added repository | | `FileChanged` | Tracked file is modified on disk | any | Re-lint, reload config | | `ConfigChange` | Settings / config file changed | any | Re-validate, warn on drift | ### Setup / bootstrap | Event | Fires When | Matcher | Typical Use | |-------|-----------|---------|-------------| | `Setup` | First-run / initialization | any | Project bootstrap | | `InstructionsLoaded` | CLAUDE.md or `.claude/rules/*.md` loaded into context | load reason | Verify presence of mandatory rules | ## Hook Handler Types Claude Code supports five handler `type` values in `hooks.json`: | Type | Purpose | Required fields | |------|---------|-----------------| | `command` | Run a shell script / binary | `command` (path + args) | | `http` | Call a local or remote HTTP endpoint | `url` | | `prompt` | Inject a prompt to the fast inline model and use its verdict | `prompt` | | `agent` | Spawn an experimental tool-using subagent to evaluate the event | `prompt` | | `mcp_tool` | Invoke an already-connected MCP tool | `server`, `tool`; optional `input` object | `command` remains the default and ai-toolkit's hook entries all use it. The other types are documented here so you can author them by hand when needed. `PermissionRequest` accepts `prompt` hooks but skips `agent` hooks. Both model-switch events accept `command`, `http`, and `mcp_tool` only. MCP tool hooks use `input` for arguments; `arguments` is not a supported handler field. MCP hooks on `SessionStart` cannot run at launch before servers connect, and `Setup` always skips them. Use command hooks for required initialization. Source: https://code.claude.com/docs/en/hooks (reviewed 2026-09-23). ## Workflow 1. **Capture intent** -- ask: what should the hook do? Which lifecycle event? 2. **Select event** -- pick from the Supported Hook Events table above 3. **Define matcher** -- tool name for PreToolUse/PostToolUse, empty for global 4. **Write script** -- create `app/hooks/{event-name-kebab}.sh` 5. **Register in hooks.json** -- add entry to `app/hooks.json` 6. **Validate** -- run `scripts/validate.py` ## Hook Script Conventions - Location: `app/hooks/{event-name-kebab}.sh` - Shebang: `#!/bin/bash` - Header comment: script name, purpose, event, matcher - Respect `TOOLKIT_HOOK_PROFILE` env var (`minimal` = skip non-essential hooks) - Exit 0 on success; use exit 2 for a blocking verdict on supported events. Other non-zero codes generally report an error without blocking. - Output goes to Claude's context as plain text - No external dependencies -- bash builtins and coreutils only - Keep output concise -- hooks fire frequently ## hooks.json Entry Format ```json { "_source": "ai-toolkit", "matcher": "", "hooks": [ { "type": "command", "command": "\"$HOME/.softspark/ai-toolkit/hooks/{script-name}.sh\"" } ] } ``` Required fields: - `_source`: always `"ai-toolkit"` (used by merge/strip logic) - `matcher`: tool name or regex for Pre/PostToolUse, empty string for global events - `hooks[].type`: `"command"`, `"http"`, `"prompt"`, `"agent"`, or `"mcp_tool"` (ai-toolkit uses `"command"`) - `hooks[].command`: path to script using `$HOME/.softspark/ai-toolkit/hooks/` prefix (for `type: command`) Optional fields (read from Claude Code docs, not emitted by ai-toolkit by default): Common to every handler type: - `hooks[].if`: one permission-rule filter (e.g. `"Bash(git push*)"`); evaluated only for tool events - `hooks[].timeout`: seconds to wait before canceling the handler (type and event defaults apply if omitted) - `hooks[].statusMessage`: short message surfaced in the UI while the hook runs - `hooks[].once`: run once per session; only honored in skill frontmatter and ignored in settings files or agent frontmatter Command-handler fields: - `hooks[].args`: argument vector for exec form. Prefer exec form when a command uses `${CLAUDE_PROJECT_DIR}`, `${CLAUDE_PLUGIN_ROOT}`, or `${CLAUDE_PLUGIN_DATA}` path placeholders - `hooks[].async`: run in the background without blocking; background hooks cannot return decisions - `hooks[].asyncRewake`: run in the background and wake Claude on exit code 2; implies `async` - `hooks[].shell`: choose `bash` or `powershell` for shell form; ignored when `args` selects exec form Prompt and agent handlers both require `hooks[].prompt`; `type: agent` does not accept an agent name field. Agent handlers are experimental, so prefer `command` for production enforcement. `MessageDisplay` has no matcher, runs once per rendered batch in interactive sessions, and defaults to a 10-second timeout. Its `displayContent` output changes only the screen text; Claude and the transcript retain the original response. `DirectoryAdded` is always asynchronous and non-blocking regardless of the handler configuration. ## Script Template ```bash #!/bin/bash # {script-name}.sh — {One-line purpose}. # # Fires on: {EventName} # Matcher: {matcher or "all"} # Skipped when TOOLKIT_HOOK_PROFILE=minimal. PROFILE="${TOOLKIT_HOOK_PROFILE:-standard}" [ "$PROFILE" = "minimal" ] && exit 0 # --- Hook logic here --- exit 0 ``` ## Rules - **MUST** use one script per hook entry — no inline multi-line commands inside `hooks.json` - **MUST** keep `Pre*` hooks fast and deterministic — they gate every matching tool call, slow hooks throttle the whole agent - **NEVER** write secrets, tokens, or credentials to stdout — hook output is injected into LLM context and can be extracted - **NEVER** exit non-zero from a `Post*` or `Stop` hook unless you intend to block further processing; exit 0 is the safe default - **CRITICAL**: respect the `TOOLKIT_HOOK_PROFILE` env var. Profile `minimal` must be a no-op for non-essential hooks. - **MANDATORY**: test the script standalone (`bash app/hooks/{name}.sh`) before adding it to `hooks.json` ## Gotchas - `PreToolUse` hooks that exit non-zero **block** the tool call. A slow or flaky hook (network call, lock contention) becomes a DoS against Claude's own workflow. Keep Pre hooks to pure-bash checks of local state. - Hook output (stdout) is injected verbatim into the model's context. A hook that runs `git log --all` prints hundreds of lines the model then has to wade through — be surgical, print only what matters. - The path in `hooks.json` is resolved relative to the user's machine, not the ai-toolkit repo. Use `$HOME/.softspark/ai-toolkit/hooks/.sh` as the canonical location (installer symlinks there). - `SessionStart` with matcher `startup|compact` fires on both fresh starts AND after context compaction. Hooks that assume "new session" will mis-fire after compaction — check for explicit context markers if the distinction matters. - Bash hooks on Windows (without WSL) will not run. If the hook must work cross-platform, wrap it in a Node or Python script and call from the bash stub — or flag the hook as `posix-only` in the description. ## Validation Checklist After creating the hook: - [ ] Script exists in `app/hooks/` and is executable (`chmod +x`) - [ ] Entry added to `app/hooks.json` with `_source: "ai-toolkit"` - [ ] Event name matches a supported lifecycle event - [ ] `scripts/validate.py` passes - [ ] Script runs without errors: `bash app/hooks/{name}.sh` - [ ] Hook count in README.md and docs updated if needed ## When NOT to Use - For a **skill** (slash command) — use `/skill-creator` - For an **agent** definition — use `/agent-creator` - For a git pre-commit hook (not a Claude Code hook) — use `/git-mastery` or `scripts/install_git_hooks.py` - For one-off automation that is not tied to a Claude Code event — use a plain shell script outside the toolkit - To modify an existing toolkit hook — edit the file directly; this skill is create-only