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.
npx claudepluginhub xxkeefer/skills --plugin metaCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.