# Hooks in Claude Code: A Beginner's Guide

> Source: <https://dev.to/alimurrazi/hooks-in-claude-code-a-beginners-guide-5bae>
> Published: 2026-09-19 14:14:25+00:00

Your `.claude/settings.json` can ask before `git push` — but that's where a permission's job ends. It's a gate, not a worker: it can't format a file the moment it's edited, catch a missing test before code ships, or keep a log of what actually happened in a session. None of that is "is this call allowed" — it's "let me run my own logic around this event, whatever that logic needs to do."

That's where hooks come in: they let you run your own code at specific points in Claude Code's workflow — for security checks, sure, but just as often for formatting, automation, or just keeping a record.

That's the gap a hook fills — not "does this call match a permission rule?" but "inspect what's actually happening, then decide what to do about it."

A hook is a script that runs automatically at a specific moment in Claude's workflow — not a declarative pattern match, but code you write that checks whatever it needs to check, then acts on what it finds.

Claude Code fires hooks at specific moments. Two matter most starting out:

`PreToolUse`` PostToolUse`
One of the jobs a hook can take on — a `PreToolUse` hook on `git push` that checks whether the files being pushed have a matching test file changed alongside them. For this example, assume the project follows a simple convention: `books.ts` has a corresponding `books.test.ts` (same `books` endpoint from the earlier articles) — real projects vary (`.spec.ts`, `BookServiceTests.cs`, `test_books.py`), so this is a toy heuristic demonstrating what a hook can do, not a general-purpose test-verification system.

```
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash(git push *)",
        "hooks": [
          { "type": "command", "command": "./scripts/check-tests-before-push.sh" }
        ]
      }
    ]
  }
}
```

`check-tests-before-push.sh` diffs the push against the base branch, lists the changed source files, and for each one checks whether a matching test file also changed. If a source file changed with no matching test file touched, the script exits non-zero — which causes the `PreToolUse` hook to prevent the push.

📄 **Full script:** [https://github.com/Alimurrazi/claude-demo-book-api/blob/master/scripts/check-tests-before-push.sh](https://github.com/Alimurrazi/claude-demo-book-api/blob/master/scripts/check-tests-before-push.sh)

It's a nudge, not a guarantee — it only checks that a test file changed, not that it actually covers the new logic. A real coverage guarantee would mean running your suite with a coverage tool instead; skipped here for simplicity.

⚠️ This only governs Claude, not everyone else. A PreToolUse hook fires only on a push when Claude Code session runs itself — not a manual push, not one from a third-party Git client (GitHub Desktop, GitKraken etc.), and not from another agent. It's not a real git hook like .git/hooks/pre-push; for simplicity, this article skips setting that up, so anything outside Claude Code stays unchecked.

Lives in the same `.claude/settings.json` as your permission rules — hooks and permissions share the file, they just do different jobs inside it.

The test-before-push check is one example, not the whole picture. Once a hook can run any script, at any lifecycle moment, a lot opens up:

`git push`, running `npm run build` and blocking the push if it fails`Edit`, scanning new content for anything that looks like a real API key before it lands
Same mechanism throughout — a script, a lifecycle moment, and some custom logic. Only the script changes. And it's not only about blocking, either — a hook can just as easily run automation, format a file, or collect information, the way the bullets above show.

A hook runs unconditionally, every time it matches — there's no judgment call, which is exactly the point, but also the risk. A hook with a bug can block legitimate work just as easily as it blocks a real problem, and it'll do it silently unless you test it.

Run `claude --debug` while you're writing a new hook, before you trust it in a real session.

*Want more — the full event list, hooks that modify a call instead of just blocking it, or chaining multiple hooks? Worth its own deep-dive later; this is what you need to get started.*

*📦 Full example: [https://github.com/Alimurrazi/claude-demo-book-api/tree/article-3-hooks](https://github.com/Alimurrazi/claude-demo-book-api/tree/article-3-hooks)*

**Previously in this series:**
