From meta
Scaffold a Claude Code hook holder from scratch -- hook script, installer skill, README, and (if config-driven) config files. Mirrors /write-a-skill but for hooks. Use when the user says "write a hook", "create a hook", "new hook", or wants to author a PreToolUse/PostToolUse/Stop hook holder rather than a skill.
How this skill is triggered — by the user, by Claude, or both
Slash command
/meta:write-a-hookThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Author a new hook holder from scratch. A holder is a self-contained directory under `hooks/<name>/`
Author a new hook holder from scratch. A holder is a self-contained directory under hooks/<name>/
that ships everything needed to install and run one hook: the script, an installer skill, a README,
and config when behaviour is data-driven. The working reference is
hooks/tool-policy/ -- read it before drafting.
$ARGUMENTS may contain:
hooks/<name>/)If anything is missing, use AskUserQuestion to fill gaps. Then invoke /grill-it to pin the
design. A hook needs more decided up front than a skill does:
PreToolUse, PostToolUse, Stop, SubagentStop, UserPromptSubmit,
SessionStart, PreCompact. Determines when it fires.Bash, Write|Edit|MultiEdit, *). Some
events take no matcher.python3 for parsing/JSON logic, shell for trivial
greps. Stay agnostic to the project being guarded.Done when: event, matcher, decision, config-or-static, fail mode, and language are all settled.
Before scaffolding:
hooks/ -- does this duplicate an existing hook's job? tool-policy
already covers runner-redirection and command-blocking via config; prefer adding a rule there
over a new holder.utility/ -- setup-git-guardrails, setup-skill-tally, etc. are
lighter inline-script installers. If the hook is trivial and static, that pattern may fit better
than a full holder.hooks/<name>/setup-<name> (the SKILL.md name)Present findings. If there's overlap, discuss extending the existing hook vs a new holder.
Create hooks/<name>/ with the files the design calls for. Minimum is the script + installer skill
Contract (current Claude Code hooks): the script reads the tool call as JSON on stdin and emits its decision as JSON on stdout. A blocking decision uses:
{ "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "..." } }
Observe-only hooks print nothing (or non-blocking output). Honour the fail mode from Step 1 --
unparseable input or missing config should not false-fire on a fail-open guard. Copy the parsing
and output shape from tool_policy.py rather than
re-deriving it.
<name>.config.json -- the live config. Ships empty/inert: the hook does nothing until
the user fills it in.<name>.example.json -- a worked config to copy rules from. Not loaded by the hook.Add a companion Write|Edit|MultiEdit guard that denies tool edits to the runtime files, and have
the main script reject Bash commands that name the config. See tool-policy's
protect_guard.py and note the documented
asymmetry between the shell and tool vectors -- state any gap you leave.
Done when: the holder dir holds a working script (and config pair, if applicable) honouring the contract and fail mode.
Write hooks/<name>/SKILL.md as the setup-<name> installer. Mirror tool-policy's installer:
~/.claude/hooks/<name>/, wired into
~/.claude/settings.json) or current project (.claude/hooks/<name>/, wired into
.claude/settings.local.json).hooks.<Event> entry into the chosen settings file. Merge,
don't overwrite; don't duplicate existing entries./hooks to reload), and how to
populate/activate config-driven behaviour.Each step gets a Done when line.
Done when: running the installer would copy the files and wire the hook into either scope.
/setup-<name> and by hand), config
schema and resolution order (if any), self-protection, and behaviour notes (fail mode, output
contract). tool-policy's README is the template.<name>_test.py (or equivalent) -- run the script against fixtures covering each decision
path and the fail-open/closed edge. Runs standalone, exits non-zero on failure.Done when: README matches the shipped files and tests pass against the script.
hooks/ section of the root CLAUDE.md layout if it warrants a
mention, and list /write-a-hook itself in meta/CLAUDE.md the first time this skill ships.Hook:
<name>Event/matcher: {event} / {matcher} Files: {list under hooks//}{brief summary of what it does}
Ask:
If the holder grows two distinct jobs (e.g. a PreToolUse guard and an unrelated Stop
reporter), make two holders. One holder, one coherent hook.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub xxkeefer/skills --plugin meta