DEV Community

brian austin
brian austin

Posted on

Claude Code hooks: auto-format, auto-lint, auto-test on every save

Claude Code hooks: auto-format, auto-lint, auto-test on every save

If you've used Claude Code for more than a few sessions, you've hit this pattern:

  1. Claude writes a function
  2. You run the linter — 12 warnings
  3. You tell Claude to fix them
  4. Claude fixes them and breaks a test
  5. Repeat

Hooks eliminate this loop. Instead of telling Claude to fix things after they break, you wire up hooks that run automatically — before Claude commits to an approach.

What are Claude Code hooks?

Hooks are shell commands that Claude Code runs at specific points in its workflow. Think of them as lifecycle events:

  • PreToolUse — runs before Claude uses a tool (file write, bash, etc.)
  • PostToolUse — runs after Claude uses a tool
  • Stop — runs when Claude finishes a task
  • Notification — runs when Claude sends you a message

You configure them in your settings.json.

The self-healing loop setup

Here's the hooks config I use on every project:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "npm run lint --silent 2>&1 | head -20"
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "npm test --silent 2>&1 | tail -10"
          }
        ]
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

What this does:

  • Every time Claude writes or edits a file → lint runs automatically
  • When Claude finishes a task → tests run automatically
  • Output is fed back into Claude's context

Claude sees the lint output and fixes violations before moving to the next file. It sees test failures and backtracks.

Auto-format hook

For Prettier users:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "prettier --write $CLAUDE_TOOL_OUTPUT_PATH 2>&1"
          }
        ]
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

$CLAUDE_TOOL_OUTPUT_PATH is the file Claude just wrote. Prettier formats it in place immediately after every write.

TypeScript type-check hook

For TypeScript projects, type errors caught early save enormous context waste:

{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit|MultiEdit",
        "hooks": [
          {
            "type": "command",
            "command": "npx tsc --noEmit 2>&1 | head -30"
          }
        ]
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

Note the head -30 — you want to show Claude enough to understand the error, not flood its context with 200 lines of cascading type failures.

The notification hook (for long tasks)

For tasks that take 10+ minutes, you can get a terminal bell or system notification:

{
  "hooks": {
    "Notification": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "echo '\a'"
          }
        ]
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

Or on macOS with osascript:

{
  "command": "osascript -e 'display notification \"Claude needs input\" with title \"Claude Code\"\'"  
}
Enter fullscreen mode Exit fullscreen mode

Combining hooks with CLAUDE.md

Hooks define when things run. CLAUDE.md defines how Claude behaves. They complement each other:

CLAUDE.md:

# Code standards
- Always run prettier before marking a task done
- If lint fails after a write, fix before proceeding
- Run tests after every component change
Enter fullscreen mode Exit fullscreen mode

settings.json hooks: enforce this automatically, even if Claude forgets.

The CLAUDE.md is the contract. The hooks are the enforcement.

One thing to watch

Hooks add latency. If your lint takes 8 seconds and Claude writes 20 files, that's 160 seconds of hook time per task. Keep hook commands fast:

  • Use --silent to suppress progress output
  • Use head -N to cap output length
  • Use file-specific commands (prettier --write $FILE) not full project scans
  • For slow type-checks, hook on Stop instead of PostToolUse

The rate limit problem

Hooks make Claude Code sessions longer and more intensive. A session that used to take 20 minutes of Claude time might now take 35 minutes with auto-linting and auto-testing feedback loops.

This accelerates how fast you hit rate limits.

The fix most Claude Code power users use: set ANTHROPIC_BASE_URL to a proxy that removes the rate limits.

export ANTHROPIC_BASE_URL=https://simplylouie.com
Enter fullscreen mode Exit fullscreen mode

At ✌️2/month (vs $20/month for Claude.ai Pro), it removes the interruptions without changing anything about how Claude Code works — same model, same quality, just no 'overloaded' stops mid-hook-loop.


What hooks are you running? Drop them in the comments — I'll add the best ones to a follow-up article.

Top comments (0)