#claude-code#hooks#automation#ci-cd#advanced#developer-tools

Claude Code Hooks: Automate Linting, Testing, and Deployment in Your Terminal

Claude Code hooks let you trigger custom scripts before and after AI actions — auto-lint on save, run tests before commits, deploy on merge. Here's how to set them up.

AI Builder ClubApril 12, 20264 min read

Claude Code hooks are event-driven scripts that run automatically when Claude Code performs certain actions. Think git hooks, but for your AI coding agent.

They're defined in .claude/hooks.json at your project root, and they unlock something powerful: Claude Code doesn't just write code — it enforces your quality standards without you asking.


What Are Hooks?

A hook is a script that triggers on a Claude Code event. The events:

| Event | When it fires | |-------|--------------| | beforeWrite | Before Claude Code writes/modifies a file | | afterWrite | After Claude Code writes/modifies a file | | beforeCommand | Before Claude Code runs a shell command | | afterCommand | After Claude Code runs a shell command |

Each hook gets context about what's happening — which file, what command, etc. — and can either proceed, modify the action, or block it.


Setup

Create .claude/hooks.json in your project root:

{
  "hooks": {
    "afterWrite": [
      {
        "name": "auto-lint",
        "script": "npx eslint --fix $CLAUDE_FILE_PATH",
        "description": "Auto-fix lint issues after every file write"
      }
    ]
  }
}

That's it. Now every time Claude Code writes or modifies a file, ESLint runs on it automatically.


Hook 1: Auto-Lint After Every Write

The most immediately useful hook. Claude Code writes good code, but it occasionally misses your specific ESLint rules — unused imports, wrong quote style, missing semicolons.

{
  "hooks": {
    "afterWrite": [
      {
        "name": "auto-lint-fix",
        "script": "npx eslint --fix $CLAUDE_FILE_PATH 2>/dev/null || true",
        "description": "Auto-fix lint on every file change"
      }
    ]
  }
}

The || true ensures the hook doesn't block Claude Code if ESLint has unfixable issues. It silently fixes what it can.

Impact: Eliminates the "run lint at the end and fix 40 issues" problem. Every file is clean as it's written.


Hook 2: Format with Prettier

If your team uses Prettier (and you should), auto-format every file Claude Code touches:

{
  "afterWrite": [
    {
      "name": "auto-format",
      "script": "npx prettier --write $CLAUDE_FILE_PATH 2>/dev/null || true",
      "description": "Format with Prettier after writes"
    }
  ]
}

Stack this with the lint hook — Prettier runs first (formatting), then ESLint (logic issues):

{
  "afterWrite": [
    {
      "name": "auto-format",
      "script": "npx prettier --write $CLAUDE_FILE_PATH 2>/dev/null || true"
    },
    {
      "name": "auto-lint",
      "script": "npx eslint --fix $CLAUDE_FILE_PATH 2>/dev/null || true"
    }
  ]
}

Hooks run in order. Format first, lint second.


Hook 3: Type-Check After Writes

Catch TypeScript errors immediately:

{
  "afterWrite": [
    {
      "name": "type-check",
      "script": "npx tsc --noEmit --pretty 2>&1 | head -20",
      "description": "Surface type errors after changes"
    }
  ]
}

The head -20 limits output so it doesn't flood Claude Code's context. It sees the first errors and can fix them immediately in the same session.


Hook 4: Run Related Tests

When Claude Code modifies a file, automatically run the tests for that file:

#!/bin/bash
# .claude/scripts/run-related-tests.sh

FILE_PATH=$1
TEST_FILE="${FILE_PATH%.ts}.test.ts"
TEST_FILE_ALT="${FILE_PATH%.tsx}.test.tsx"

if [ -f "$TEST_FILE" ]; then
  npx jest "$TEST_FILE" --no-coverage 2>&1 | tail -10
elif [ -f "$TEST_FILE_ALT" ]; then
  npx jest "$TEST_FILE_ALT" --no-coverage 2>&1 | tail -10
fi
{
  "afterWrite": [
    {
      "name": "run-related-tests",
      "script": "bash .claude/scripts/run-related-tests.sh $CLAUDE_FILE_PATH"
    }
  ]
}

Now Claude Code gets immediate test feedback. If it breaks a test, it sees the failure output and can fix it in the same turn — no manual intervention.


Hook 5: Prevent Modifications to Protected Files

Some files should never be casually edited. Use beforeWrite to block changes:

#!/bin/bash
# .claude/scripts/protect-files.sh

PROTECTED_FILES=(
  "middleware.ts"
  "app/api/webhook/stripe/route.ts"
  ".env"
  ".env.local"
)

for protected in "${PROTECTED_FILES[@]}"; do
  if [[ "$1" == *"$protected"* ]]; then
    echo "BLOCKED: $1 is a protected file. Please confirm before modifying."
    exit 1
  fi
done
{
  "beforeWrite": [
    {
      "name": "protect-critical-files",
      "script": "bash .claude/scripts/protect-files.sh $CLAUDE_FILE_PATH"
    }
  ]
}

If the hook exits with code 1, Claude Code stops and reports the block. It'll ask you whether to proceed.


Combining Hooks: A Full Quality Pipeline

Here's a production-ready .claude/hooks.json that enforces quality at every step:

{
  "hooks": {
    "beforeWrite": [
      {
        "name": "protect-critical-files",
        "script": "bash .claude/scripts/protect-files.sh $CLAUDE_FILE_PATH"
      }
    ],
    "afterWrite": [
      {
        "name": "format",
        "script": "npx prettier --write $CLAUDE_FILE_PATH 2>/dev/null || true"
      },
      {
        "name": "lint",
        "script": "npx eslint --fix $CLAUDE_FILE_PATH 2>/dev/null || true"
      },
      {
        "name": "type-check",
        "script": "npx tsc --noEmit --pretty 2>&1 | head -20"
      },
      {
        "name": "related-tests",
        "script": "bash .claude/scripts/run-related-tests.sh $CLAUDE_FILE_PATH"
      }
    ]
  }
}

Every file Claude Code touches gets: protected file check → formatted → linted → type-checked → tested. Automatically. Zero manual effort.


Performance Considerations

Hooks add latency to every Claude Code action. Keep them fast:

  • Lint/format on single files, not the whole project
  • Use || true so non-critical hooks don't block the session
  • Limit output with head or tail — Claude Code ingests all output
  • Skip hooks for trivial files — add conditionals for file extensions (.ts, .tsx only)
  • Move heavy checks to afterCommand — run the full test suite after git commit, not after every file write

The Mindset Shift

Without hooks, Claude Code is a talented developer who doesn't follow your process. You review everything manually at the end.

With hooks, Claude Code is a talented developer working inside your CI pipeline. Every action is automatically validated. You review less because issues are caught and fixed in real-time.

This is where AI coding starts to feel like having a real teammate — one who follows the process every time, without reminders.

If you want to see hooks configurations from real projects and discuss automation patterns with other builders, join AI Builder Club. We share configs, scripts, and what actually works in production.

Get the free AI Builder Newsletter

Weekly deep-dives on AI tools, automation workflows, and builder strategies. Join 5,000+ readers.

No spam. Unsubscribe anytime.

Go deeper with AI Builder Club

Join 1,000+ ambitious professionals and builders learning to use AI at work.

  • Expert-led courses on Cursor, MCP, AI agents, and more
  • Weekly live workshops with industry builders
  • Private community for feedback, collaboration, and accountability