Featured image of post Configuring Claude Code Python UV Hooks and Skills

Configuring Claude Code Python UV Hooks and Skills

Troubleshooting Guide: Using Hooks to Force Claude Code to Use uv for Python Dependency Management

语速

My project uses uv to manage Python dependencies, but Claude Code habitually defaults to python or pip install. I tried using Skills and Hooks to enforce this standard and encountered quite a few pitfalls.

Goal

  • Create a Skill: Inform Claude that the project uses uv
  • Create a Hook: Intercept python/pip commands
  • Verify effectiveness

Troubleshooting Journey

First Attempt: Wrong Skill File Structure (Commit 8a05759)

1
2
❌ .claude/skills/python-uv.md
✅ .claude/skills/python-uv/SKILL.md

The frontmatter also needed changes:

1
2
3
4
5
6
7
8
9
---
# Wrong
description: Python dependency and execution management using uv
location: project

# Correct
name: python-uv
description: Python dependency and execution management using uv. Use when adding Python packages, running Python scripts, or managing Python dependencies. Enforces uv instead of pip/python commands.
---

Key points:

  • Filename must be SKILL.md, placed in the corresponding directory
  • Frontmatter requires a name field
  • description should be detailed to help Claude identify when to trigger

Second Attempt: Hook Only Warns Without Blocking (Commit d250c3b)

Initially wrote the Hook in Bash, which only displayed warnings but didn’t prevent execution. Also tried configuring environment.PATH, which didn’t work.

Third Attempt: Wrong Hook Exit Code (Commit d3790a4)

Tried using exit 1 to block commands, but it still didn’t work.

Correct exit codes:

  • exit 0: Allow execution
  • exit 1: Hook fails, but doesn’t block the tool
  • exit 2: Actually blocks tool execution ✅

Fourth Attempt: Fixed Skill Format (Commit 2595b68)

Found the file structure was wrong, changed to the correct skills/xxx/SKILL.md format.

Fifth Attempt: Rewrote Hook in Python (Commit dcc726d)

Bash JSON parsing was too fragile, ultimately rewrote in Python:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#!/usr/bin/env python3
"""
Hook to block python/python3 commands and enforce uv usage.
"""
import json
import sys
import re

try:
    # Correctly parse JSON input
    input_data = json.load(sys.stdin)
except json.JSONDecodeError as e:
    print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
    sys.exit(1)

# Get tool name and command
tool_name = input_data.get("tool_name", "")
tool_input = input_data.get("tool_input", {})
command = tool_input.get("command", "")

# Only process Bash tool
if tool_name != "Bash" or not command:
    sys.exit(0)

# Check if using python/python3
if re.search(r'\bpython3?\b', command):
    # Whitelist: allow version checks etc.
    if re.search(r'(--version|--help|which python)', command):
        sys.exit(0)

    # Block command
    error_msg = (
        f"\n❌ BLOCKED: This project requires using 'uv'\n\n"
        f"Original command:\n  {command}\n\n"
        f"Suggested replacement:\n  {suggested}\n"
    )

    print(error_msg, file=sys.stderr)
    sys.exit(2)  # Use exit 2 to block tool invocation

# Allow other commands
sys.exit(0)

Configuration file (.claude/settings.json):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",  // Simplified matcher format
        "hooks": [
          {
            "type": "command",
            "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/pre-bash"
          }
        ]
      }
    ]
  }
  // Remove ineffective environment.PATH configuration
}

Notes:

  • matcher can directly specify the tool name, no need for expressions
  • Use $CLAUDE_PROJECT_DIR to reference the project path
  • environment.PATH configuration doesn’t work in Hooks, don’t waste time on it

Final File Structure

1
2
3
4
5
6
7
.claude/
├── skills/
│   └── python-uv/
│       └── SKILL.md
├── hooks/
│   └── pre-bash          # Python script
└── settings.json

Testing

✅ Block regular commands:

1
2
$ python test.py
❌ BLOCKED: Use 'uv run' instead

✅ Allow version checks:

1
2
$ python --version
Python 3.11.0

✅ Skill active: When asked “how to run Python scripts,” Claude will proactively suggest using uv run.

Key Points

Claude Code Sometimes Doesn’t Proactively Query Specifications

I explicitly requested creating Hooks and Skills, but Claude Code started writing code without first checking the official documentation. This led to:

  • File structure errors multiple times
  • Wrong exit codes
  • Incorrect JSON parsing approach

If it had used WebFetch to read the official Hook and Skill documentation before starting, all these pitfalls could have been avoided.

This isn’t about users needing to read documentation, but rather that AI agents should check specifications before executing unfamiliar tasks.

Skills and Hooks Can Indeed Enforce Claude Code’s Behavior

Hooks can constrain Python commands to provide the correct command suggestions. This is also the approach used in Kilo Code.

References