Skip to content
techpotions

Claude Code hooks, with the missing manual.

Eight hooks we run on production client work: a secret shield, a danger blocker, lockfile and dev-server guards, a typecheck gate that makes Claude fix its own type errors, and more. Plus the reference the searches keep asking for: every event, the exact stdin JSON, exit codes, and blocking patterns.

Download free8 hooks · plain Node · zero dependencies · MIT
What's inside

Eight hooks that hold the line.

HookEventWhat it does
secret-shieldPreToolUseBlocks commands and file writes containing API keys, tokens, private keys, credentialed URLs
danger-blockerPreToolUseBlocks rm -rf /, force-push to main, DROP DATABASE, disk-level writes, fork bombs
pnpm-guardPreToolUseReads the lockfile, rejects the wrong package manager with the right one named
dev-server-guardPreToolUseStops the agent launching npm run dev in its own shell
coauthor-scrubberPreToolUseRejects commits carrying the Co-Authored-By trailer; Claude retries clean
typecheck-gateStopRuns tsc --noEmit at turn end; errors bounce back to Claude to fix
format-on-writePostToolUsePrettier on each touched file, only in repos that use Prettier
turn-timerUserPromptSubmit + StopShows how long each response took

Ships as a ready-to-enable plugin, and every script also works standalone with a five-line settings.json entry. Plain Node, no dependencies, readable in one sitting.

The concept

Why hooks beat instructions.

You can tell Claude “never use npm here” in CLAUDE.md, and it will comply, usually. A hook removes the usually: it is a script the harness itself runs around every tool call, and when it says deny, the command does not execute. Instructions are advice; hooks are law.

The elegant part is what happens after a block. The hook’s reason is fed straight back into Claude’s context, so a good reason (“this repo uses pnpm, found pnpm-lock.yaml”) means the model immediately retries the right way. You are not fighting the agent; you are giving it guardrails it can read.

Reference

The events, and when they fire.

EventFiresTypical use
PreToolUseBefore every tool callGuardrails: allow, deny, or ask, per call
PostToolUseAfter a tool call succeedsFormatters, linters, notifications
UserPromptSubmitWhen you send a promptInject context (stdout becomes context), stamp timers
StopWhen Claude finishes a turnQuality gates: block the stop until checks pass
SessionStart / SessionEndOnce per sessionLoad state, write session logs
SubagentStart / SubagentStopAround subagent runsTrack or restrict delegated work
PermissionRequestOn a permission promptAuto-answer known-safe prompts
FileChanged / ConfigChangeAsync, on changesWatchers, cache busting

Register hooks in settings.json under the event name, with a matcher against the tool (exact name, regex, or *):

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "node /path/to/danger-blocker.js" }
        ]
      }
    ]
  }
}
Reference

The stdin/stdout contract.

Every hook reads one JSON object on stdin and may answer with JSON on stdout. For PreToolUse, tool_input carries the exact arguments about to run, and your verdict goes back as a permissionDecision:

// stdin (what your script receives):
{
  "session_id": "abc123",
  "cwd": "/repo",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": { "command": "npm install lodash" }
}

// stdout (how you block it):
{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "This repo uses pnpm (found pnpm-lock.yaml).
      Re-run with pnpm so the lockfile stays consistent."
  }
}

Exit codes are the shorthand alternative: exit 0 processes your stdout, exit 2 blocks the action with stderr fed back to Claude, anything else is a non-blocking warning. Stop hooks use a top-level decision instead:

// A Stop hook that refuses to let the turn end with type errors:
{
  "decision": "block",
  "reason": "tsc --noEmit reports 3 errors after your changes.
    Fix them before finishing: src/auth.ts(42,7): error TS2339..."
}

One warning from production: a Stop hook that blocks must check the stop_hook_active flag on its input, or a persistent failure loops forever. The typecheck gate in this pack shows the guard.

Download free8 hooks · plain Node · zero dependencies · MIT
FAQ

Common questions.

  • 01What are hooks in Claude Code?

    Hooks are scripts Claude Code runs at fixed points in its loop: before a tool call (PreToolUse), after one (PostToolUse), when you submit a prompt, when a turn ends (Stop), and a dozen more events. Unlike instructions in CLAUDE.md, which the model can overlook, hooks are enforced by the harness itself: a hook that denies a command means the command does not run, every time.

  • 02How do I set up hooks in Claude Code?

    Two ways. Add a hooks object to ~/.claude/settings.json (or .claude/settings.json in a repo for your whole team) mapping an event and a tool matcher to your script. Or install hooks as a plugin, which is what this pack ships: copy the folder, register it in settings, restart. The /hooks command in any session shows exactly what is active.

  • 03How does a hook block an action?

    A PreToolUse hook answers on stdout with a permissionDecision of deny (plus a reason), or simply exits with code 2 with the reason on stderr. The reason is fed back to Claude, which is the clever part: tell the model why it was blocked ("this repo uses pnpm") and it retries correctly on its own. A Stop hook can return decision: block to send Claude back to work, which is how the typecheck gate in this pack makes it fix its own type errors.

  • 04What JSON does a hook receive?

    Every hook gets a JSON object on stdin with session_id, cwd, transcript_path, hook_event_name, and permission_mode. Tool hooks add tool_name and tool_input (the exact arguments: the Bash command string, the file path and content for Write). That last field is what makes real guardrails possible: you inspect precisely what is about to run.

  • 05Do these hooks work on Windows?

    Yes. All eight are plain Node scripts with zero dependencies, and Node is already there because Claude Code runs on it. Tested on Windows, macOS, and Linux; no bash-isms, no jq.

  • 06Can I use just one hook from the pack instead of all eight?

    Yes. Every script is standalone. Copy the one file you want anywhere and wire it in settings.json with a five-line hooks entry (the README shows the exact snippet), or install the plugin and delete the entries you do not want from hooks/hooks.json.

Want guardrails designed for your codebase?

These eight are the general-purpose set. The interesting ones are specific: hooks that know your deploy pipeline, your data boundaries, your compliance rules. That is the work we do for clients. techpotions is a small studio; you talk to the people who build.