Anything you keep asking the agent to do “every time” is a candidate for a hook: a command Claude Code runs itself at a fixed point in the session. These patterns cover the usual cases. Put project ones in .claude/settings.json so the whole team gets them, and personal ones (sounds, notifications) in ~/.claude/settings.json.

When to use it

When a rule must hold on every turn and is easy to check mechanically: formatting, type errors, failing tests, protected files, a reminder that must survive compaction. If the rule needs judgment case by case, keep it in CLAUDE.md.

How to do it

  1. Format after every edit. PostToolUse with matcher Edit|Write, running Prettier (or your formatter) on tool_input.file_path. Nothing shows in the conversation on success; check the file.
  2. Type-check after every edit. PostToolUse on Edit|Write running npx tsc --noEmit 1>&2 || exit 2. The edit is already on disk, so exit 2 cannot undo it, but the compiler errors go to Claude on stderr and it fixes them in the next step. This turns the advice in Verifying agent output into something that happens without asking.
  3. Do not let Claude stop on red tests. A Stop hook of type agent runs the test suite in a subagent and answers ok: false with a reason when tests fail, so Claude keeps working. Give it a longer timeout than the 60-second default. A Stop hook that blocks eight times in a row is overridden, so it cannot loop forever.
  4. Guard files and commands. PreToolUse on Edit|Write (or Bash) that checks the path or command and exits 2 with a reason on stderr: .env, lockfiles, .git/, a commit to main. Claude reads the reason and looks for another way. Keep permission rules as the hard gate; the hook adds an explanation Claude can act on.
  5. Restore context after compaction. SessionStart with matcher compact. Whatever the command prints to stdout is added to the context, for example git log --oneline -5 or “use pnpm, not npm”. For context that every session needs from the start, CLAUDE.md is the simpler place.
  6. Trim noisy output before Claude reads it. A PreToolUse hook on Bash can rewrite the command through updatedInput, for example piping a test run through a filter for failing lines, so a log of thousands of lines costs hundreds of tokens instead of tens of thousands. Third-party tools such as rtk package this as a ready hook. See Reducing token usage.
  7. Get notified. Notification with an empty matcher fires when Claude waits for input or permission; narrow it with matchers such as permission_prompt or idle_prompt. Run a desktop notification command, or a tool such as peon-ping, which plays a sound and sets the terminal tab title.

Example

A Node + TypeScript service, .claude/settings.json:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "jq -r '.tool_input.file_path' | xargs npx prettier --write" },
          { "type": "command", "command": "npx tsc --noEmit 1>&2 || exit 2" }
        ]
      }
    ],
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          { "type": "command", "command": "node \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.ts" }
        ]
      }
    ],
    "SessionStart": [
      {
        "matcher": "compact",
        "hooks": [
          { "type": "command", "command": "echo 'Use pnpm. Run pnpm test before saying done.'; git log --oneline -5" }
        ]
      }
    ]
  }
}

When Claude writes await fs.promises.exists(path), the type-check hook fails with the compiler error, Claude reads it and switches to fs.promises.access, before you look at the diff. The protect-files.ts script is on the hooks page.

Common mistakes

  • Exiting 1 to block. Only exit 2 or a JSON deny blocks. Exit 1 is a hook error, and the action goes ahead.
  • Relying on a hook as the only lock. A crash or timeout lets the action through unless the hook has "onFailure": "block". Pair important blocks with permission rules.
  • Two hooks rewriting the same command. With two updatedInput hooks on Bash, the last to finish wins at random. Keep one rewriter per tool.
  • Slow checks on every edit. A full test run after each Edit makes every step slow. Put fast checks (format, type-check) on PostToolUse and the slow suite on Stop.
  • Forgetting chmod +x. A script hook that is not executable does not run.

Citations

  1. Automate actions with hooks
  2. Hooks reference
  3. Manage costs effectively: reduce token usage
  4. rtk repository
  5. peon-ping repository