# Claude Code Hooks for uv Projects


[Claude Code](https://code.claude.com/docs/en/overview) supports [custom hooks](https://code.claude.com/docs/en/hooks-guide) that guide the AI agent toward project-specific workflows. For Python projects using [uv](https://pydevtools.com/handbook/reference/uv.md), hooks can prevent Claude from falling back to `pip` and `python` commands.

> [!NOTE]
> This post builds on the [interceptor approach](https://pydevtools.com/blog/interceptors.md) for teaching AI agents about uv. Interceptors provide runtime feedback; hooks prevent problematic commands before execution.

## Understanding Claude Code Hooks

Claude Code supports three types of hooks that run at different stages of interaction:

- PreToolUse: Executes before Claude runs commands, allowing intervention and command transformation
- PostToolUse: Runs after tool execution to provide feedback or additional context
- Notification: Monitors Claude's message context for keywords and displays contextual reminders

These Python scripts analyze Claude's intended actions and respond accordingly, enabling project-specific guidance without modifying Claude Code itself.

## The uv Hook Configuration

Developer Glenn Matlin created a [hook configuration for uv projects](https://gist.github.com/glennmatlin/fadc41edc3bb9ff68ff9cfa5d6b8aca7) that steers Claude toward uv-first workflows.

### Pre-Tool Use Hook

The pre-tool hook intercepts commands before execution, checking for Python-related operations that should use uv instead:

```python
#!/usr/bin/env python3
import json
import sys

def main():
    # Read the tool use data from stdin
    data = json.load(sys.stdin)

    # Check if this is a Bash/Run command
    if data.get("tool") not in ["Bash", "Run"]:
        sys.exit(0)

    command = data.get("parameters", {}).get("command", "")

    # Detect problematic patterns
    patterns = {
        "python ": "uv run",
        "pip install": "uv add",
        "pytest": "uv run pytest",
        "ruff": "uv run ruff"
    }

    for old, new in patterns.items():
        if old in command:
            print(f"⚠️  Detected '{old}' in command.", file=sys.stderr)
            print(f"💡 In uv projects, use '{new}' instead.", file=sys.stderr)
            sys.exit(1)

if __name__ == "__main__":
    main()
```

This hook blocks commands using traditional Python patterns and tells Claude what to use instead.

### Notification Hook

The notification hook monitors Claude's messages for Python-related keywords and provides contextual reminders:

```python
#!/usr/bin/env python3
import json
import sys

def main():
    data = json.load(sys.stdin)
    message = data.get("content", "").lower()

    keywords = {
        "install": "Remember: Use 'uv add' for package installation",
        "run python": "Remember: Use 'uv run python' for scripts",
        "virtual environment": "Remember: uv handles environments automatically"
    }

    for keyword, reminder in keywords.items():
        if keyword in message:
            print(f"📝 {reminder}", file=sys.stderr)
            break

if __name__ == "__main__":
    main()
```

Unlike the pre-tool hook, this one doesn't block execution—it just prints reminders as Claude discusses Python operations.

## Installation

### Create Hook Directory

Claude Code looks for hooks in `~/.claude/hooks/`. Create this directory:

```bash
mkdir -p ~/.claude/hooks
```

### Add Hook Scripts

Download and save the hook scripts:

```bash
# Pre-tool use hook
curl -o ~/.claude/hooks/pre_tool_use_uv.py \
  https://gist.githubusercontent.com/glennmatlin/fadc41edc3bb9ff68ff9cfa5d6b8aca7/raw/pre_tool_use_uv.py

# Notification hook
curl -o ~/.claude/hooks/notification_uv.py \
  https://gist.githubusercontent.com/glennmatlin/fadc41edc3bb9ff68ff9cfa5d6b8aca7/raw/notification_uv.py

# Make them executable
chmod +x ~/.claude/hooks/pre_tool_use_uv.py
chmod +x ~/.claude/hooks/notification_uv.py
```

### Configure Settings

Create or update `~/.claude/settings.json` to register the hooks:

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash|Run",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/pre_tool_use_uv.py",
            "timeout": 10
          }
        ]
      }
    ],
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/notification_uv.py",
            "timeout": 5
          }
        ]
      }
    ]
  }
}
```

### Restart Claude Code

Restart Claude Code for the hooks to take effect. The hooks will now monitor all Claude operations in your terminal session.

## How It Works in Practice

When working on a uv project, the hooks intervene at two points:

Before execution, the pre-tool hook prevents traditional commands:

```bash
$ python script.py
⚠️  Detected 'python ' in command.
💡 In uv projects, use 'uv run' instead.
```

During conversation, the notification hook provides contextual reminders:

```
Claude: To install the package, I'll need to...
📝 Remember: Use 'uv add' for package installation
```

Claude learns the expected patterns without manual correction on every command.

## Comparison with Interceptors

Both hooks and [interceptors](https://pydevtools.com/blog/interceptors.md) guide AI agents toward uv, but they work differently:

| Aspect | Hooks | Interceptors |
|--------|-------|--------------|
| Scope | Claude Code only | Any AI agent or terminal session |
| Integration | Native Claude Code feature | PATH manipulation with [direnv](https://pydevtools.com/handbook/reference/direnv.md) |
| Feedback timing | Before command execution | During command execution |
| Configuration | Global `~/.claude/` directory | Per-project `.envrc` files |
| Complexity | Python scripts with JSON parsing | Simple shell scripts |

Hooks provide deeper integration with Claude Code but require Claude-specific configuration. Interceptors work with any tool but require direnv and per-project setup.

## Extending the Hooks

The hook scripts can be customized for project-specific patterns. For example, to add guidance about [dependency groups](https://docs.astral.sh/uv/concepts/projects/#dependency-groups):

```python
# In pre_tool_use_uv.py, add to patterns:
patterns = {
    # ... existing patterns ...
    "pip install -r requirements-dev.txt": "uv sync --group dev"
}
```

Or to add reminders about [tool execution](https://docs.astral.sh/uv/guides/tools/):

```python
# In notification_uv.py, add to keywords:
keywords = {
    # ... existing keywords ...
    "ruff check": "Consider: 'uv tool run ruff' for one-off usage"
}
```

These modifications allow tailoring the hooks to match team conventions and project structure.

## Learn More

- [How to write Claude Code hooks for Python projects](https://pydevtools.com/handbook/how-to/how-to-write-claude-code-hooks-for-python-projects.md) for a comprehensive guide covering PreToolUse, PostToolUse, and UserPromptSubmit hooks
- [Claude Code Hooks Documentation](https://code.claude.com/docs/en/hooks)
- [Use interceptors to teach Claude Code to use uv](https://pydevtools.com/blog/interceptors.md)
- [How to configure Claude Code to use uv](https://pydevtools.com/handbook/how-to/how-to-configure-claude-code-to-use-uv.md)
- [uv Reference Documentation](https://pydevtools.com/handbook/reference/uv.md)
