Tutorials

How to Integrate AI Rules with Husky Hooks

Git hooks with Husky: validate AI rules before every commit, run lint-staged on AI-generated code, and prevent commits that bypass your coding standards.

5 min readยทJuly 5, 2025

Husky pre-commit: the last line of defense. Rule validation, lint-staged auto-fix, and commit message enforcement โ€” before code enters the repo.

Pre-commit validation, lint-staged auto-fix, commitlint, pre-push version checks, and emergency bypass policy

Git Hooks: The Last Line of Defense

Git hooks run at specific points in the git workflow: before commit (pre-commit), before push (pre-push), and during commit message creation (commit-msg). Husky: makes git hooks easy to configure and share across the team (hooks defined in package.json or .husky/ directory, installed automatically on npm install). For AI rules: git hooks provide the last line of defense before code enters the repository.

The defense-in-depth model: the AI generates code following the rules (first defense). The developer reviews the code (second defense). Prettier formats the code (third defense). ESLint checks the code (fourth defense). The git hook: validates that the rules file exists, the code passes lint, and the commit message follows conventions (fifth defense). By the time the commit is created: the code has passed through 5 layers of quality assurance.

Why hooks for AI rules: the AI generates convention-compliant code 90% of the time. The developer catches most issues in review. But: occasionally, AI-generated code that bypasses conventions makes it through. The pre-commit hook: catches it before it enters the repository. Once in the repository: the non-compliant code affects other developers and may be deployed. The hook: prevents this.

Step 1: Pre-Commit Hook for Rule Validation

Install Husky: pnpm add -D husky. Initialize: pnpm exec husky init. This creates the .husky/ directory. Create the pre-commit hook: .husky/pre-commit with the validation commands. The pre-commit hook: runs before every git commit. If it exits with a non-zero code: the commit is blocked.

Hook content: the pre-commit hook runs two checks. (1) Rule file exists: verify CLAUDE.md is present and not empty. If missing: block the commit with a message ('CLAUDE.md is missing. AI rules are required.'). (2) Lint-staged: run ESLint and Prettier on staged files only (not the entire codebase). This ensures: every committed file passes lint and formatting. Lint-staged: runs fast because it only checks files in the current commit.

Lint-staged configuration: in package.json: 'lint-staged': { '*.{ts,tsx}': ['eslint --fix', 'prettier --write'], '*.{json,md}': ['prettier --write'] }. This configuration: auto-fixes lint and formatting issues on staged files before committing. The developer: commits clean code without manually running lint. AI-generated code that has minor lint issues: auto-fixed at commit time. AI rule: 'Lint-staged + Husky pre-commit: auto-fixes minor issues and blocks major issues. The developer commits clean code every time.'

๐Ÿ’ก Lint-Staged = Fast Because It Only Checks Changed Files

Running ESLint on the entire codebase: 30-60 seconds (blocks the developer's flow). Running lint-staged on only the files in the current commit: 2-5 seconds. Same coverage for new changes. Same fix capability. 10x faster. Lint-staged makes pre-commit hooks practical: developers do not disable them because they are fast enough to be invisible. The speed: is why lint-staged is essential, not just convenient.

Step 2: Commit Message Validation

Conventional commits: if your CLAUDE.md specifies a commit message convention (feat:, fix:, refactor:, test:, docs:, chore:), enforce it with a commit-msg hook. Install commitlint: pnpm add -D @commitlint/cli @commitlint/config-conventional. Create .husky/commit-msg with: pnpm exec commitlint --edit $1. Create commitlint.config.js: module.exports = { extends: ['@commitlint/config-conventional'] }.

Why commit message validation matters for AI: Claude Code and other AI tools generate commit messages. The commit-msg hook: ensures AI-generated commit messages follow the conventional commits format. If the AI generates 'Updated user service': the hook rejects it. If the AI generates 'feat(users): add email validation to registration': the hook accepts it. The hook: enforces the convention regardless of whether a human or AI wrote the message.

Custom commit message rules: if your team has specific commit message requirements beyond conventional commits (e.g., ticket number required: 'feat(users): add validation [PROJ-123]'), configure commitlint with a custom rule. The AI rule in CLAUDE.md: 'Commit messages: conventional format with ticket number. Example: feat(auth): add MFA support [AUTH-456].' The commit-msg hook: validates the format. Together: the AI generates the correct format AND the hook verifies it. AI rule: 'AI rule for the format + hook for the enforcement = consistent commit messages whether written by human or AI.'

โš ๏ธ Regularly Bypassing Hooks = The Hook Is Broken

If developers regularly use --no-verify: the hook is the problem, not the developers. Common causes: the hook is too slow (runs full lint instead of lint-staged), the hook blocks on non-critical issues (formatting that could be auto-fixed), or the hook has false positives (rejects valid code). Fix the hook: make it fast (lint-staged), auto-fix what is fixable (--fix flag), and eliminate false positives. A hook that developers do not bypass: is a hook that adds value without adding friction.

Step 3: Advanced Hook Patterns

Pre-push hook for rule version: before pushing to the remote, verify the CLAUDE.md version is current. In .husky/pre-push: a script that reads the CLAUDE.md version header and compares against the expected version (from a VERSION file or a remote check). If outdated: block the push with a message ('Your AI rules are outdated. Run rulesync pull to update.'). This ensures: no one pushes code that was generated with old rules.

Selective hook execution: not every file change needs full validation. Lint-staged: runs lint only on changed files (fast). Rule validation: runs only if CLAUDE.md was modified or if it is missing (fast check). Type checking (tsc --noEmit): runs on the full project (slower โ€” consider running only on pre-push, not pre-commit). AI rule: 'Pre-commit: fast checks (lint-staged, rule file existence). Pre-push: slower checks (type checking, rule version validation). Balance speed with thoroughness.'

Bypassing hooks in emergencies: git commit --no-verify skips hooks. This should be rare (emergency fixes where the hook blocks a critical deployment). Your CLAUDE.md should include a note: 'Hooks should not be bypassed except in emergencies. If bypassing: document the reason in the commit message. Follow up with a proper commit that passes all hooks.' AI rule: 'Bypassing hooks: emergency-only. If you find yourself bypassing regularly: fix the hook (too slow? too strict?) instead of working around it.'

โ„น๏ธ AI-Generated Commit Messages Also Need Validation

Claude Code generates commit messages. Without a commit-msg hook: the AI might generate 'Updated stuff' (too vague) or 'refactored the entire authentication module to use a new pattern' (too long, no conventional prefix). With commitlint: the AI learns to generate 'feat(auth): add MFA support' (correct format). If the AI generates a rejected format: Claude Code retries with the correct format. The hook: trains the AI through feedback.

Husky Integration Summary

Complete Husky + AI rules integration.

  • Pre-commit: validate CLAUDE.md exists + lint-staged (ESLint --fix + Prettier --write on staged files)
  • Commit-msg: commitlint validates conventional commit format for both human and AI messages
  • Pre-push: rule version validation. Block push if rules are outdated
  • Lint-staged: auto-fixes minor lint and formatting issues. Fast (staged files only)
  • Defense-in-depth: AI rules โ†’ developer review โ†’ Prettier โ†’ ESLint โ†’ git hooks. 5 layers
  • Custom commit messages: AI rule for format + hook for enforcement = consistent messages
  • Speed: pre-commit is fast (lint-staged). Pre-push can be slower (type checking)
  • Bypass: --no-verify for emergencies only. Fix the hook if bypassing becomes frequent