Claude Code Hooks for uv Projects
Claude Code supports custom hooks that can guide the AI agent toward project-specific workflows. For Python projects using uv, these hooks ensure the AI consistently uses modern tooling patterns instead of falling back to traditional pip and python commands.
Note
This post builds on the interceptor approach for teaching AI agents about uv. While interceptors provide runtime feedback, Claude Code hooks offer deeper integration by preventing 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 hooks are Python scripts that analyze and respond to Claude’s intended actions, enabling project-specific guidance without modifying Claude Code itself.
The uv Hook Configuration
Developer Glenn Matlin created a comprehensive hook configuration for uv projects that demonstrates how to guide 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:
#!/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 containing traditional Python patterns and provides immediate feedback about the uv-equivalent command.
Notification Hook
The notification hook monitors Claude’s messages for Python-related keywords and provides contextual reminders:
#!/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 which blocks execution, the notification hook provides gentle reminders as Claude discusses Python operations.
Installation
Create Hook Directory
Claude Code looks for hooks in ~/.claude/hooks/. Create this directory:
mkdir -p ~/.claude/hooksAdd Hook Scripts
Download and save the hook scripts:
# 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.pyConfigure Settings
Create or update ~/.claude/settings.json to register the hooks:
{
"hooks": {
"PreToolUse": [
{
"command": "~/.claude/hooks/pre_tool_use_uv.py",
"tools": ["Bash", "Run"],
"timeout": 10000
}
],
"Notification": [
{
"command": "~/.claude/hooks/notification_uv.py",
"timeout": 5000
}
]
}
}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 provide multi-layered guidance:
Before execution, the pre-tool hook prevents traditional commands:
$ 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 installationThis approach creates a consistent learning environment where Claude adapts to uv workflows without manual correction on every interaction.
Comparison with Interceptors
Both hooks and interceptors 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 |
| 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:
# 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:
# 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.