This page documents the Claude Code hooks system as implemented in superpowers: the hooks.json configuration file, the run-hook.cmd polyglot wrapper, the session-start script, and the JSON output schema. This covers only the Claude Code and Cursor hook mechanism. For OpenCode's equivalent context injection (which uses experimental.chat.system.transform instead of hooks), see OpenCode Integration. For the broader session bootstrap lifecycle across all platforms, see Session Lifecycle and Bootstrap.
Claude Code's plugin hook system allows a plugin to inject context into a session at specific lifecycle events. Superpowers uses this to inject the using-superpowers meta-skill at session start. The hooks are defined in hooks.json, dispatched through run-hook.cmd (a polyglot wrapper that works on both Windows and Unix), and implemented in session-start (a bash script that reads skill content and emits JSON).
Hook flow summary:
Sources: hooks/hooks.json1-16 hooks/run-hook.cmd1-46 hooks/session-start1-51
The file hooks/hooks.json1-16 registers a single SessionStart hook:
| Field | Value | Description |
|---|---|---|
hooks.SessionStart | array | List of hook rule objects for this event |
matcher | "startup|resume|clear|compact" | Regex matched against session trigger type |
type | "command" | Run the hook as a shell command |
command | '${CLAUDE_PLUGIN_ROOT}/hooks/run-hook.cmd' session-start | The command to execute |
async | false | Hook runs synchronously; output is consumed before session proceeds |
The CLAUDE_PLUGIN_ROOT environment variable is injected by Claude Code and points to the installed plugin directory. The single quotes around the path prevent premature shell expansion.
The matcher startup|resume|clear|compact covers all session entry points: a new session start, resuming a prior session, clearing context, and compacting context. This ensures the bootstrap content is always present.
Sources: hooks/hooks.json1-16
hooks/run-hook.cmd1-46 is a single file that is simultaneously valid CMD batch syntax and bash syntax. Claude Code on Windows runs hook commands through cmd.exe, which cannot execute .sh files directly. The .cmd extension tells Windows to run it as a batch file, while Unix shells interpret the same file as a bash script.
Why extensionless hook scripts?
Hook scripts like session-start deliberately have no .sh extension. Claude Code on Windows auto-prepends bash to any command containing .sh, which interferes with the wrapper's own bash dispatch logic. The wrapper handles bash invocation explicitly.
Diagram: run-hook.cmd Execution Logic
Sources: hooks/run-hook.cmd1-46
hooks/run-hook.cmd1-40 — The heredoc trick ': << CMDBLOCK' makes CMD treat the line as a label and skip processing the << 'CMDBLOCK' part. CMD then executes the batch lines that follow. The CMDBLOCK label later signals exit /b, stopping CMD before it reaches the Unix section.
The Windows fallback chain, in order:
C:\Program Files\Git\bin\bash.exe (hooks/run-hook.cmd21-24)C:\Program Files (x86)\Git\bin\bash.exe (hooks/run-hook.cmd25-28)bash via where bash on %PATH% (hooks/run-hook.cmd31-34)0 — plugin loads without SessionStart context (hooks/run-hook.cmd37-39)The silent exit on no-bash-found is intentional: failing loudly would break plugin initialization on Windows systems without Git Bash.
hooks/run-hook.cmd42-46 — On Unix, : << 'CMDBLOCK' is a no-op followed by a heredoc. Everything up to CMDBLOCK is consumed and discarded. Execution resumes at line 43:
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SCRIPT_NAME="$1"
shift
exec bash "${SCRIPT_DIR}/${SCRIPT_NAME}" "$@"
This resolves the absolute path of the hooks/ directory, then execs bash with the named script (e.g., session-start) passing remaining arguments through.
Sources: hooks/run-hook.cmd1-46 docs/windows/polyglot-hooks.md1-60
hooks/session-start1-51 is the bash script that run-hook.cmd dispatches to. It reads skill content from disk and emits a JSON object to stdout for Claude Code to consume.
PLUGIN_ROOT from SCRIPT_DIR/../ (hooks/session-start7-8)~/.config/superpowers/skills exists, build a warning string (hooks/session-start11-15)cat the using-superpowers/SKILL.md file (hooks/session-start18)escape_for_json on both content and warning (hooks/session-start33-34)<EXTREMELY_IMPORTANT> XML tags (hooks/session-start35)cat <<EOF outputs the final JSON object (hooks/session-start41-49)hooks/session-start23-31 implements JSON string escaping using pure bash parameter substitution — no sed, awk, or other external tools. This is required for compatibility with Git Bash on Windows, where PATH may not include those utilities at hook invocation time.
Each substitution is a single pass over the string at the C level:
| Replacement | Purpose |
|---|---|
\\ → \\\\ | Escape backslashes (must be first) |
" → \" | Escape double quotes |
\n → \n (literal) | Escape newlines |
\r → \r (literal) | Escape carriage returns |
\t → \t (literal) | Escape tabs |
The function uses printf '%s' rather than echo to avoid platform-specific newline behavior.
Sources: hooks/session-start1-51 docs/windows/polyglot-hooks.md108-134
The session-start script outputs a single JSON object to stdout. Claude Code (and Cursor) each expect a different field shape for context injection, so the script emits both:
| Field | Consumer | Description |
|---|---|---|
additional_context | Cursor | Top-level field for context string injection |
hookSpecificOutput.hookEventName | Claude Code | Identifies the hook event type |
hookSpecificOutput.additionalContext | Claude Code | The injected context string |
The context string value is the session_context variable constructed at hooks/session-start35 which wraps the full using-superpowers skill content inside <EXTREMELY_IMPORTANT> tags.
Diagram: JSON Output to Platform Consumers
Sources: hooks/session-start41-49
If the directory ~/.config/superpowers/skills exists, session-start appends a warning to the injected context hooks/session-start11-15 This directory was used by an older version of superpowers before Claude Code's native skills system existed. The warning instructs the agent to notify the user in its first reply and tells them to move custom skills to ~/.claude/skills.
The warning text is wrapped in <important-reminder> tags so the agent recognizes it as an instruction requiring immediate acknowledgment.
Sources: hooks/session-start11-15
.gitattributes1-16 enforces LF line endings on all hook-related files:
| Pattern | Setting | Reason |
|---|---|---|
*.cmd | text eol=lf | Polyglot file parsed by both CMD and bash; CRLF breaks bash heredoc parsing |
hooks/session-start | text eol=lf | Bash script must have LF endings |
*.sh | text eol=lf | General rule for all shell scripts |
The .cmd file having LF endings is counterintuitive — Windows CMD handles LF-only files correctly for batch execution, and bash requires LF for the heredoc delimiter CMDBLOCK to be recognized.
Sources: .gitattributes1-16 docs/windows/polyglot-hooks.md86-92
Diagram: Hooks System File Relationships
Sources: hooks/hooks.json1-16 hooks/run-hook.cmd1-46 hooks/session-start1-51 .gitattributes1-16
Refresh this wiki
This wiki was recently refreshed. Please wait 3 days to refresh again.