Hook Writer
Turn a plain-language automation request — 'format every file Claude edits', 'block writes to migrations', 'notify me when input is needed' — into a working Claude Code hook: the right event, a safe tested script, and the settings.json registration at the right scope. Use when you want a hook but don't want to hand-write the matcher, stdin JSON parsing, and exit-code plumbing.
npx agentscamp add skills/hook-writerInstall to ~/.claude/skills/hook-writer/SKILL.md
Describe the automation you want and this skill ships the hook: it picks the correct event (PreToolUse to gate, PostToolUse to react, UserPromptSubmit to validate, Notification/Stop for signals), writes a hardened script that parses the stdin JSON and uses exit codes correctly, registers it in the right settings file with a scoped matcher and timeout, and tells you exactly how to verify it fires.
Give this skill a sentence like "run prettier on every file Claude edits" or "never let Claude touch .env files, and tell it why" and it produces the complete hook: event choice, matcher, hardened script, settings registration, and a verification step. Hooks are the right tool for rules that must hold every time — this skill removes the plumbing tax of writing one.
When to use this skill
- You want an automatic action around Claude Code's lifecycle: format after edits, run affected tests, log tool calls, send a desktop notification, enforce a freeze window.
- You want to block something deterministically: edits to protected paths, dangerous Bash patterns, prompts containing secrets.
- You have a hook that misbehaves and want it diagnosed and rewritten safely.
When NOT to use this skill
- The rule is a judgment call ("prefer small functions") — that's a CLAUDE.md instruction, not a hook. See Claude Code Hooks for the dividing line.
- You're gating which tools may run at all with static patterns — plain permission rules do that with zero code; hooks are for logic patterns can't express.
- You're bundling hooks for distribution to other repos — write them here, then package with plugin-scaffolder.
Instructions
- Restate the automation as event + condition + action. Name the lifecycle moment (before a tool call? after? on prompt submit? when Claude finishes?), the condition (which tools, paths, or patterns), and the action (run, block, notify, log). If the user's request maps to two events, prefer the narrower one — gate with
PreToolUse, react withPostToolUse. - Choose blocking semantics deliberately. Only some events can block (
PreToolUse,UserPromptSubmit). For a blocking hook, decide fail-open vs fail-closed on script errors and say which you chose: formatters fail open (exit 0 on error), guardrails fail closed (exit 2 on any doubt). Blocking output goes to stderr so Claude learns why and adjusts. - Write the script defensively. Read the event JSON from stdin (
jq -r '.tool_input.file_path // empty'), quote every expansion, handle missing fields, and keep it fast — hooks run inline with the session. Place it at.claude/hooks/<name>.shand make it executable. Never interpolate model-controlled values into shell unquoted. - Register it at the right scope. Project-wide rules go in
.claude/settings.json(team-shared); personal automation in~/.claude/settings.json; machine-local experiments in.claude/settings.local.json. Add the matcher (Edit|Write,Bash,mcp__server__tool, or*) and atimeout. Show the exact JSON block being added and merge it without clobbering existing hooks. - Verify it fires. Trigger the event once (e.g. have Claude edit a scratch file) and confirm the effect; run
/hooksto confirm registration and source file. For a blocking hook, also verify the allowed path still works — overblocking is the most common hook bug. - Hand over the off-switch. Note how to disable it (remove the JSON block, or
"disableAllHooks": truetemporarily) and what its failure mode looks like in practice.
WARNING
A hook is arbitrary code running with the user's credentials on every matching event. Keep secrets out of hook scripts, treat tool input as untrusted data, and never write a blocking hook whose error path silently allows what it was built to stop.
TIP
When the user's rule is expressible as a permission pattern (deny: ["Read(./.env)"]), say so and offer the rule instead — fewer moving parts beats a script doing the same job.
Output
The executable hook script at .claude/hooks/<name>.sh, the exact settings JSON registered (with scope stated and why), a one-command verification you ran or the user can run, and the disable/rollback instructions — everything needed to trust the hook or remove it.
Related
- Claude Code Hooks: Automate Formatting, Tests, and GuardrailsHow Claude Code hooks work — the major hook events, the settings.json configuration shape, exit codes and JSON output, plus three hooks worth copying.
- Claude Code Settings & Permissions: settings.json ExplainedEvery Claude Code settings file and which one wins, the permission-rule syntax with its Bash matching gotchas, permission modes, and a safe starter settings.json.
- Claude Settings AuditorAudit every Claude Code settings layer — user, project, local, and managed — and report the effective merged configuration with its risks: over-broad Bash allows, missing deny rules for secrets, bypassPermissions defaults, unvetted MCP servers and hooks, and rules that never match. Use before trusting a new repo's checked-in settings, or to harden your own before handing the agent more autonomy.
- Plugin ScaffolderScaffold a complete, valid Claude Code plugin from a description — the .claude-plugin/plugin.json manifest, component directories (skills, agents, commands, hooks, MCP config), portable ${CLAUDE_PLUGIN_ROOT} wiring, a local test loop with --plugin-dir, and a marketplace.json for distribution. Use when turning scattered .claude/ customizations into one installable, versioned package.
- Setup Claude CIWire Claude Code into this repo's CI the safe way — install the GitHub App or scaffold the workflow YAML, scope permissions to the minimum, set secrets correctly, and verify with a real trigger.