Use Claude Code Hooks to automate your workflow.
Claude Code hooks make Python workflows deterministic via events, matchers, commands, and exit codes for notifications, package guards, and formatting.
Key Takeaways
- Claude Code hooks automate repeated safety and cleanup steps by tying commands to events, making assistant behavior deterministic instead of prompt-dependent.
- The article demonstrates three practical hooks: a desktop notification on `Stop`, a pip guard on `PreToolUse`, and automatic Ruff formatting on `PostToolUse`.
- Hooks are configured in `.claude/settings.json` using three parts: an event, an optional matcher, and a command, with exit codes allowing hooks to block or redirect actions.
- The workflow uses a reproducible `uv`-managed Python project and requires specific versions, including Claude Code, Python, `uv`, and Ruff.
Meta description: Learn how Claude Code hooks automate desktop notifications, approved tool guards, and Ruff formatting using events, matchers, commands, and exit codes.
Tags: Claude Code hooks, Python automation, Ruff, uv, developer workflows
Featured image: abstract friendly illustration of three connected workflow nodes with bell, lock, and code-format icons, no people, no logos, no text
For Python developers who already use Claude Code, the main challenge is not writing the next block of code. The bigger problem is making the coding assistant reliably do the same safety and cleanup steps every time. Real Python’s guide to Claude Code hooks shows how to turn tasks such as finishing a long edit, avoiding unapproved package installation, and formatting changed files into automatic actions. Instead of asking Claude to remember your rules, you register commands to specific events, so the behavior becomes deterministic.
Set up the project and versions
The tutorial starts with a small `uv`-managed project, the kind many Python developers would actually use in real work. You create it with `uv init --no-package hooks-demo`, then enter the folder with `cd hooks-demo`. Running `uv sync` creates the project’s virtual environment early, which helps the hooks run cleanly from the beginning.
The project layout includes a `pyproject.toml`, a starter `main.py`, and a few dotfiles. Before building hooks, the guide assumes you are comfortable with intermediate Python, the terminal, and reading a bit of JSON. It also assumes you have spent some time in Claude Code. If Claude Code is new to you, the guide points to Real Python’s How to Use Claude Code to Write and Debug Python tutorial and the Getting Started With Claude Code video course.
The version requirements matter because the hooks are meant to work in a reproducible setup. You need Claude Code 2.1.187 or later, Python 3.12 or later, uv 0.11.6 or later, and Ruff 0.15.20 or later. If you do not have uv installed yet, Real Python has a separate guide on installing uv. Ruff can be installed later, when you build the formatting hook.
The guide also offers free sample code for three hooks: a desktop notifier, a pip guard, and an auto-formatter. Those three hooks are the practical backbone of the tutorial.
Build the first Stop hook
The first useful improvement is a desktop notification. When you give Claude a long task and step away, it is easy to miss the moment Claude finishes. That delay can make a short pause feel like a lost block of focus. A hook on the `Stop` event closes that gap because `Stop` fires every time Claude finishes a response.
Notifications work differently across operating systems, so the tutorial avoids hard-coding one command for all machines. Instead, it uses a small Python script saved at `.claude/hooks/notify_desktop.py`. The script reads the event data that Claude Code sends on standard input, then chooses the right notifier for the current platform.
On macOS, the script uses `osascript` to display a notification. On Linux, it uses `notify-send`. On Windows and other systems, or if the notifier is not installed, the hook falls back to a plain-text message so it does not interrupt the session. The guide mentions that a native Windows toast could also be wired up with a PowerShell module such as BurntToast.
The sample script prints a simple fallback message, “Claude just finished responding,” when no notification system is available. That fallback is important: a hook should not break your workflow just because your operating system or desktop environment is unusual.
Register the hook and understand its three parts
Project-level hooks live in `.claude/settings.json`. Each hook is made of three parts: an event, an optional matcher, and a command. The event is the moment the hook runs, such as `Stop`. The matcher can limit the hook to certain tools, which is useful when you do not want the same hook to run after every possible action. The command is the shell command that the hook executes.
This structure is why hooks are more reliable than prompts. A prompt asks the model to do something. A hook says that when a specific event occurs, the command runs. That distinction is the core idea of Claude Code hooks. You are no longer relying on the assistant to remember to format, guard, or notify. You are wiring those actions to events.
The tutorial also introduces the exit-code contract. A hook can influence what happens next by returning a specific exit status. This lets a hook block or redirect the action the model is about to take. For example, a guard hook can stop an unapproved command before it runs, while a formatting hook can act after an edit has been made.
The interactive quiz titled How to Automate Your Workflow With Claude Code Hooks checks understanding of the `Stop`, `PreToolUse`, and `PostToolUse` events, as well as the exit codes that can block an action. It is a good way to confirm that you can tell the difference between “after Claude finishes,” “before a tool runs,” and “after a tool runs.”
Add PreToolUse guards and PostToolUse formatting
Once the Stop hook is working, the next two hooks show how to protect and clean up the project. The pip guard is associated with `PreToolUse`, which fires before a tool action. This makes it the right place to check whether a command should be allowed. The matcher can limit the guard to the relevant tool, and the command can use exit codes to block or redirect work that does not match your project’s approved tools.
The auto-formatter is associated with `PostToolUse`, which fires after a tool has run. This is where a hook can format the file Claude just edited. The guide shows a PostToolUse hook formatting Claude’s edit with Ruff. Because Ruff is pinned at 0.15.20 or later, the formatting behavior is tied to a known tool version, which makes the setup easier to reproduce.
Together, the three hooks form a simple workflow pattern. The Stop hook tells you when Claude is done. The PreToolUse hook prevents risky or unapproved actions. The PostToolUse hook cleans up the result so the code stays consistent with your standards. That combination is useful because it addresses three different failure points: lost attention, unsafe tool use, and inconsistent formatting.
For a reader, the practical value is straightforward. If you use Claude Code for Python, hooks move repetitive safety steps from conversation memory into the project configuration. You stop repeating instructions. You stop hoping the model will remember. Instead, the hook fires on the event you chose, every time.
Common mistakes
The first mistake is trying to solve deterministic behavior with prompts. If a step must happen every time, it should be a hook, not a reminder in the chat. The second mistake is hard-coding one notification command for every operating system. The guide avoids that by using a Python script that chooses the right notifier for macOS or Linux and falls back to plain text elsewhere.
Another mistake is forgetting the three-part structure. A hook needs the right event, the right matcher, and the right command. If the matcher is missing or too broad, the hook may run when you do not want it to. If the command is wrong, the hook may silently fail or produce noisy output.
Finally, it helps to keep the events separate in your mind. `Stop` is for when Claude finishes responding. `PreToolUse` is for actions before a tool runs, such as checking a package command. `PostToolUse` is for cleanup after a tool runs, such as formatting edited files. Confusing those events makes the workflow harder to control.
The full guide is available at https://realpython.com/claude-code-hooks/.

Comments (0)