Claude Code pre-commit hook

Block Claude from committing if lint or typecheck fails — a zero-config quality gate via settings.json.

Claude Code pre-commit hook

Hooks are shell commands the harness runs in response to Claude Code events. They fire deterministically — Claude can't forget to run them. Below is the gate I put on every project.

settings.json

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "bash -lc 'case \"$CLAUDE_TOOL_INPUT\" in *git\\ commit*) npm run lint && npm run type-check ;; esac'"
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "say -v Samantha 'Done.'"
          }
        ]
      }
    ]
  }
}

What's happening

  • PreToolUse on Bash + git commit — before any git commit runs, lint and typecheck must pass. A non-zero exit blocks the commit; Claude sees the failure and fixes it.
  • Stop — when Claude finishes a turn, a TTS voice says "Done." so I can switch tabs without watching the spinner. macOS only; swap for notify-send on Linux.

Useful events

| Event | Fires when | |---|---| | PreToolUse | Before any tool call (matcher narrows by tool name) | | PostToolUse | After a tool call returns — perfect for autoformatting after Write | | UserPromptSubmit | When you hit enter — useful for redacting secrets from outgoing prompts | | Stop | When the model returns end_turn | | SessionStart | When a new Claude Code session begins — load env, warm caches |

A real PostToolUse autoformat

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "bash -lc 'echo \"$CLAUDE_TOOL_INPUT\" | jq -r .file_path | xargs -r prettier --write'"
          }
        ]
      }
    ]
  }
}

Every Write or Edit call now ends with the file getting prettier'd. Claude never has to remember.

Two rules I follow

  1. Hooks are not Claude. Anything you express as "Claude should always do X" is actually a hook on the harness — Claude can drift, the runtime can't.
  2. Hooks must be idempotent and fast. A flaky 10s hook turns every tool call into a 10s wait.