Skill v1.0.2
currentAutomated scan100/1003 files
version: "1.0.2" 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
- 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_PROFILEenv 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
{"_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 eventshooks[].type:"command","http","prompt","agent", or"mcp_tool"(ai-toolkit uses"command")hooks[].command: path to script using$HOME/.softspark/ai-toolkit/hooks/prefix (fortype: 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 eventshooks[].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 runshooks[].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 placeholdershooks[].async: run in the background without blocking; background hooks cannot return decisionshooks[].asyncRewake: run in the background and wake Claude on exit code 2; impliesasynchooks[].shell: choosebashorpowershellfor shell form; ignored whenargsselects 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*orStophook unless you intend to block further processing; exit 0 is the safe default - CRITICAL: respect the
TOOLKIT_HOOK_PROFILEenv var. Profileminimalmust be a no-op for non-essential hooks. - MANDATORY: test the script standalone (
bash app/hooks/{name}.sh) before adding it tohooks.json
Gotchas
PreToolUsehooks 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 --allprints hundreds of lines the model then has to wade through — be surgical, print only what matters. - The path in
hooks.jsonis resolved relative to the user's machine, not the ai-toolkit repo. Use$HOME/.softspark/ai-toolkit/hooks/<name>.shas the canonical location (installer symlinks there). SessionStartwith matcherstartup|compactfires 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-onlyin the description.
Validation Checklist
After creating the hook:
- [ ] Script exists in
app/hooks/and is executable (chmod +x) - [ ] Entry added to
app/hooks.jsonwith_source: "ai-toolkit" - [ ] Event name matches a supported lifecycle event
- [ ]
scripts/validate.pypasses - [ ] 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-masteryorscripts/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