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 |
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 / AGENTS.md loaded into context |
any |
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 MCP tool directly (no subprocess) |
server, tool, arguments |
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.
Workflow
- Capture intent -- ask: what should the hook do? Which lifecycle event?
- Select event -- pick from the Supported Hook Events table above
- Define matcher -- tool name for PreToolUse/PostToolUse, empty for global
- Write script -- create
app/hooks/{event-name-kebab}.sh
- Register in hooks.json -- add entry to
app/hooks.json
- 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)
- Always
exit 0 on success (non-zero blocks the operation for Pre* hooks)
- 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
{
"_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
#!/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/<name>.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:
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
1---2name: hook-creator3description: "Create new Claude Code lifecycle hook (PreToolUse/PostToolUse/Stop/SessionStart) with bash + hooks.json. Triggers: create hook, lifecycle hook, PreToolUse, PostToolUse, hook event."4effort: high5disable-model-invocation: true6argument-hint: "[hook event or description]"7allowed-tools: Read, Write, Edit, Bash, Grep, Glob8---910# Hook Creator1112$ARGUMENTS1314Create a new Claude Code hook following ai-toolkit conventions.1516## Supported Hook Events1718### Core lifecycle1920| Event | Fires When | Matcher | Typical Use |21|-------|-----------|---------|-------------|22| `SessionStart` | Session begins, resumes, or clears | `startup\|resume\|clear` | Context injection, rules reminder |23| `SessionEnd` | Session is closing | any | Flush logs, save transcripts |24| `UserPromptSubmit` | User submits a prompt | any | Prompt governance, usage tracking |25| `Notification` | Claude sends a notification | any | OS alerts, Slack pings |26| `MessageDisplay` | Assistant message text streams in completed-line batches | none | Return `displayContent` to replace the rendered batch without changing the transcript |2728### Tool lifecycle2930| Event | Fires When | Matcher | Typical Use |31|-------|-----------|---------|-------------|32| `PreToolUse` | Before a tool executes | tool name (e.g. `Bash`) or `if:` rule | Safety guards, validation, `"defer"` for headless |33| `PostToolUse` | After a tool executes | tool name | Feedback loops, logging, format-on-save |34| `PostToolUseFailure` | After a tool fails | tool name | Failure telemetry, recovery hints |35| `PostToolBatch` | After a batch of tool calls completes | any | Batch summaries, aggregate validation |3637### Turn lifecycle3839| Event | Fires When | Matcher | Typical Use |40|-------|-----------|---------|-------------|41| `Stop` | Claude finishes responding | any | Quality checks, session save |42| `StopFailure` | Turn ends due to an API error (rate limit, auth) | any | Alerting, fallback behavior |43| `UserPromptExpansion` | Claude expands or rewrites a submitted prompt | any | Prompt policy and context shaping |4445### Subagent lifecycle4647| Event | Fires When | Matcher | Typical Use |48|-------|-----------|---------|-------------|49| `SubagentStart` | Subagent launches | any | Observability |50| `SubagentStop` | Subagent completes | any | Result validation |5152### Compaction5354| Event | Fires When | Matcher | Typical Use |55|-------|-----------|---------|-------------|56| `PreCompact` | Before context compaction; can block with exit 2 or `{"decision":"block"}` | any | Context preservation |57| `PostCompact` | After compaction completes | any | Re-inject state that was summarized away |5859### Permissions & elicitation6061| Event | Fires When | Matcher | Typical Use |62|-------|-----------|---------|-------------|63| `PermissionRequest` | Tool awaiting permission; can return `updatedInput` | any | Headless approval flows |64| `PermissionDenied` | Auto-mode classifier denied a tool call; return `{retry: true}` to allow retry | any | Coach the model, log denials |65| `Elicitation` | MCP `elicitation/create` request arrives | any | Intercept / override MCP UI prompts |66| `ElicitationResult` | Elicitation response ready to be sent back | any | Validate / transform elicitation replies |6768### Agent Teams6970| Event | Fires When | Matcher | Typical Use |71|-------|-----------|---------|-------------|72| `TaskCreated` | New task registered via `TaskCreate` | any | Audit, assignment routing |73| `TaskCompleted` | Agent Teams task finished | any | Lint, type check, notify |74| `TeammateIdle` | Agent Teams member idle | any | Completeness reminder |7576### Worktrees & environment7778| Event | Fires When | Matcher | Typical Use |79|-------|-----------|---------|-------------|80| `WorktreeCreate` | Worktree is being created; `type: "http"` can return `hookSpecificOutput.worktreePath` | any | Provision worktree dirs |81| `WorktreeRemove` | Worktree is being removed | any | Cleanup |82| `CwdChanged` | Working directory changes during a session | any | Reactive env management (e.g., direnv) |83| `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 |84| `FileChanged` | Tracked file is modified on disk | any | Re-lint, reload config |85| `ConfigChange` | Settings / config file changed | any | Re-validate, warn on drift |8687### Setup / bootstrap8889| Event | Fires When | Matcher | Typical Use |90|-------|-----------|---------|-------------|91| `Setup` | First-run / initialization | any | Project bootstrap |92| `InstructionsLoaded` | CLAUDE.md / AGENTS.md loaded into context | any | Verify presence of mandatory rules |9394## Hook Handler Types9596Claude Code supports five handler `type` values in `hooks.json`:9798| Type | Purpose | Required fields |99|------|---------|-----------------|100| `command` | Run a shell script / binary | `command` (path + args) |101| `http` | Call a local or remote HTTP endpoint | `url` |102| `prompt` | Inject a prompt to the fast inline model and use its verdict | `prompt` |103| `agent` | Spawn an experimental tool-using subagent to evaluate the event | `prompt` |104| `mcp_tool` | Invoke an MCP tool directly (no subprocess) | `server`, `tool`, `arguments` |105106`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.107108## Workflow1091101. **Capture intent** -- ask: what should the hook do? Which lifecycle event?1112. **Select event** -- pick from the Supported Hook Events table above1123. **Define matcher** -- tool name for PreToolUse/PostToolUse, empty for global1134. **Write script** -- create `app/hooks/{event-name-kebab}.sh`1145. **Register in hooks.json** -- add entry to `app/hooks.json`1156. **Validate** -- run `scripts/validate.py`116117## Hook Script Conventions118119- Location: `app/hooks/{event-name-kebab}.sh`120- Shebang: `#!/bin/bash`121- Header comment: script name, purpose, event, matcher122- Respect `TOOLKIT_HOOK_PROFILE` env var (`minimal` = skip non-essential hooks)123- Always `exit 0` on success (non-zero blocks the operation for Pre* hooks)124- Output goes to Claude's context as plain text125- No external dependencies -- bash builtins and coreutils only126- Keep output concise -- hooks fire frequently127128## hooks.json Entry Format129130```json131{132 "_source": "ai-toolkit",133 "matcher": "",134 "hooks": [135 {136 "type": "command",137 "command": "\"$HOME/.softspark/ai-toolkit/hooks/{script-name}.sh\""138 }139 ]140}141```142143Required fields:144- `_source`: always `"ai-toolkit"` (used by merge/strip logic)145- `matcher`: tool name or regex for Pre/PostToolUse, empty string for global events146- `hooks[].type`: `"command"`, `"http"`, `"prompt"`, `"agent"`, or `"mcp_tool"` (ai-toolkit uses `"command"`)147- `hooks[].command`: path to script using `$HOME/.softspark/ai-toolkit/hooks/` prefix (for `type: command`)148149Optional fields (read from Claude Code docs, not emitted by ai-toolkit by default):150151Common to every handler type:152- `hooks[].if`: one permission-rule filter (e.g. `"Bash(git push*)"`); evaluated only for tool events153- `hooks[].timeout`: seconds to wait before canceling the handler (type and event defaults apply if omitted)154- `hooks[].statusMessage`: short message surfaced in the UI while the hook runs155- `hooks[].once`: run once per session; only honored in skill frontmatter and ignored in settings files or agent frontmatter156157Command-handler fields:158- `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 placeholders159- `hooks[].async`: run in the background without blocking; background hooks cannot return decisions160- `hooks[].asyncRewake`: run in the background and wake Claude on exit code 2; implies `async`161- `hooks[].shell`: choose `bash` or `powershell` for shell form; ignored when `args` selects exec form162163Prompt 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.164165`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.166167## Script Template168169```bash170#!/bin/bash171# {script-name}.sh — {One-line purpose}.172#173# Fires on: {EventName}174# Matcher: {matcher or "all"}175# Skipped when TOOLKIT_HOOK_PROFILE=minimal.176177PROFILE="${TOOLKIT_HOOK_PROFILE:-standard}"178[ "$PROFILE" = "minimal" ] && exit 0179180# --- Hook logic here ---181182exit 0183```184185## Rules186187- **MUST** use one script per hook entry — no inline multi-line commands inside `hooks.json`188- **MUST** keep `Pre*` hooks fast and deterministic — they gate every matching tool call, slow hooks throttle the whole agent189- **NEVER** write secrets, tokens, or credentials to stdout — hook output is injected into LLM context and can be extracted190- **NEVER** exit non-zero from a `Post*` or `Stop` hook unless you intend to block further processing; exit 0 is the safe default191- **CRITICAL**: respect the `TOOLKIT_HOOK_PROFILE` env var. Profile `minimal` must be a no-op for non-essential hooks.192- **MANDATORY**: test the script standalone (`bash app/hooks/{name}.sh`) before adding it to `hooks.json`193194## Gotchas195196- `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.197- 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.198- The path in `hooks.json` is resolved relative to the user's machine, not the ai-toolkit repo. Use `$HOME/.softspark/ai-toolkit/hooks/<name>.sh` as the canonical location (installer symlinks there).199- `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.200- 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.201202## Validation Checklist203204After creating the hook:205206- [ ] Script exists in `app/hooks/` and is executable (`chmod +x`)207- [ ] Entry added to `app/hooks.json` with `_source: "ai-toolkit"`208- [ ] Event name matches a supported lifecycle event209- [ ] `scripts/validate.py` passes210- [ ] Script runs without errors: `bash app/hooks/{name}.sh`211- [ ] Hook count in README.md and docs updated if needed212213## When NOT to Use214215- For a **skill** (slash command) — use `/skill-creator`216- For an **agent** definition — use `/agent-creator`217- For a git pre-commit hook (not a Claude Code hook) — use `/git-mastery` or `scripts/install_git_hooks.py`218- For one-off automation that is not tied to a Claude Code event — use a plain shell script outside the toolkit219- To modify an existing toolkit hook — edit the file directly; this skill is create-only