Hooks execute real commands with your permissions. Start with harmless, observable checks and treat every hook script like production automation: validate input, quote variables, use explicit paths and never expose secrets in output.
1. Choose one deterministic event that should always happen
Choose an event that truly benefits from deterministic enforcement—such as blocking commits when generated files are stale—instead of turning every preference into a hook. Hooks are strongest for rules that should run every time.
2. Write the smallest script and test it manually before attaching it
Implement the smallest script that can express the check and run it directly in the shell with both passing and failing examples. Record the expected exit codes and output before Claude Code is involved.
3. Add the hook in the appropriate project or user settings file with a narrow matcher
Attach the hook to the narrowest event and matcher that covers the rule. Broad matchers increase latency and can unexpectedly block unrelated commands, so start restrictive and expand only with evidence.
4. Trigger one controlled Claude Code action and inspect input, output and exit behavior
Trigger one controlled action and inspect the actual input payload, stdout/stderr and exit status. Confirm a passing case remains unobtrusive and a failing case explains exactly what the developer should fix.
5. Keep blocking hooks explicit and document how teammates can disable or debug them
Document how to disable or debug the hook before sharing it with a team. A blocking automation without an escape hatch or clear owner can halt work even when the underlying policy is correct.
Create a project-level Claude Code hook that runs [formatter/linter/check] only after Write or Edit. Keep the matcher narrow. Show me the settings JSON and script separately, explain the exit codes, and do not add auto-approval for unrelated commands.
Attaching a wildcard matcher to a destructive script; parsing untrusted stdin unsafely; using hooks to silently approve broad shell access; hiding failures with `|| true`; committing machine-specific paths into shared settings.
Finish check
The hook fires only on the intended event, its command can be explained and reproduced outside Claude Code, failures are visible, and it does not widen unrelated permissions.