← All guides

Claude Code · July 11, 2026 · 3 min read

Make Claude Code follow your rules with hooks

CLAUDE.md and skills only suggest behavior. Hooks enforce it at fixed lifecycle points. Copy a hook that blocks .env edits even when permissions are bypassed.

The problem

You put “never touch .env files” in your CLAUDE.md. Claude Code respects it for a week, then one long session it edits the file anyway. That is not a bug. CLAUDE.md, and every skill you write, is text the model reads and usually follows. Usually is not always. Rules that must hold every single time need enforcement, not a polite request, and that is exactly what Claude Code hooks are for.

When does a rule belong in a hook?

A hook is a shell command Claude Code runs automatically at a fixed point in its lifecycle: before a tool call, after an edit, when a session starts. The trigger is guaranteed, which gives you the whole split in four words: skills suggest, hooks enforce.

The test I use: if the rule needs no judgment and must fire every time, it is a hook. If Claude should reason about how to apply it, it belongs in CLAUDE.md or a skill. Run the formatter after every edit: hook. Never touch protected files: hook. “Prefer small components”: CLAUDE.md. “Here is our deploy checklist”: skill.

An instruction is a request. A hook is a guarantee.

The hook to copy

This blocks every edit to any .env file. Paste it into .claude/settings.json in your project (commit it and the whole team gets the guardrail), or into ~/.claude/settings.json to cover all your projects:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path // empty' | grep -q '\\.env' && { echo 'Blocked: .env files are protected' >&2; exit 2; }; exit 0"
          }
        ]
      }
    ]
  }
}

Every hook receives the event as JSON on stdin. This one pulls the file path of the pending edit and, if it contains .env, exits with exit code 2: the action is blocked and your stderr message is fed back to Claude so it adjusts instead of retrying. Run /hooks inside Claude Code to confirm it registered.

The formatter version is the same pattern after the edit instead of before it: a PostToolUse hook with the same matcher and the command jq -r '.tool_input.file_path' | xargs npx prettier --write.

When a rule needs a judgment call at a guaranteed moment, there is a middle ground: a hook with "type": "prompt" sends the event to a small Claude model that returns ok or a reason to block. Deterministic trigger, model decision.

Do this now

  1. Pick the one rule Claude keeps breaking in your project.
  2. Paste the hook above into .claude/settings.json and point the grep at your protected path.
  3. Run /hooks to confirm it registered, then ask Claude to edit the file and watch the block land.

Claude Code

Turn this into a Claude skill

Paste this into Claude Code. It reads this guide and builds a skill shaped to how you work.

Read the guide at https://aiwithamirthan.com/guides/make-claude-code-follow-your-rules-with-hooks. Then build me a Claude Code skill from it, personalized to how I actually work.

1. Fetch and read the full guide at the URL above.
2. Look at my setup so the skill fits me, not a generic template: skim my repos, my CLAUDE.md / AGENTS.md files, my existing skills, and my stack.
3. Create a new skill (a SKILL.md with a clear name, description, and steps) that lets me do what this guide teaches, adapted to my tools and goals.
4. Tell me where you saved it and how to invoke it.

If anything about my setup is unclear, ask me one or two questions first.

Want to build this yourself?

Join Claude Code Academy: courses, build alongs, and builders turning AI into real systems. And I post a new build on Instagram nearly every day.