Claude Code Hooks for uv Projects
Claude Code supports custom hooks that guide the AI agent toward project-specific workflows. For Python projects using uv, hooks can prevent Claude from falling back to pip and python commands.
Note
This post builds on the interceptor approach 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 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:
#!/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:
#!/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:
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 intervene at two points:
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 installationClaude learns the expected patterns without manual correction on every command.
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.