UV-specific patterns for single-file Python scripts using inline metadata (PEP 723). Use when creating Python hooks, standalone utilities, or executable scripts in UV-managed projects.
Inherits all available tools
Additional assets for this skill
This skill inherits all available tools. When active, it can use any tool Claude has access to.
This skill documents UV-specific patterns for single-file Python scripts with inline dependency metadata. For general Python knowledge, Claude relies on base training.
Fetch when you need current UV script syntax:
What UV scripts solve: Traditional Python scripts require separate environment setup or requirements.txt files. UV's inline metadata format (PEP 723) embeds dependencies directly in the script, enabling automatic environment creation on-demand.
Key insight: UV scripts are ideal for Claude Code hooks and standalone utilities because they're self-contained and executable without external configuration.
Dependencies declared in TOML comment block at top of file:
# /// script
# dependencies = [
# "requests<3",
# "rich",
# ]
# ///
import requests
import rich
# Your script logic here
Critical requirement: The dependencies field MUST be provided even if empty:
# /// script
# dependencies = []
# ///
Optional Python version requirement:
# /// script
# requires-python = ">=3.12"
# dependencies = ["requests<3"]
# ///
Run with uv run:
uv run script.py
UV automatically:
Important behavior: When inline metadata exists, project dependencies are ignored (no need for --no-project).
For standalone executable scripts (common for hooks):
#!/usr/bin/env -S uv run --script
# /// script
# dependencies = ["rich"]
# ///
import rich
if __name__ == "__main__":
rich.print("[green]Hello from UV script![/green]")
Make executable:
chmod +x script.py
./script.py # Runs directly without `uv run` prefix
Why this works: Shebang enables PATH-based execution and simplifies hook scripts.
Use UV scripts for:
Example use case (hook script):
#!/usr/bin/env -S uv run --script
# /// script
# dependencies = ["ruff"]
# ///
import subprocess
import sys
result = subprocess.run(["ruff", "check", "."], capture_output=True)
sys.exit(result.returncode)
Don't use UV scripts for:
Problem: Omitting dependencies field causes UV to fail.
# /// script
# requires-python = ">=3.11"
# ///
# ERROR: Missing required 'dependencies' field
Solution: Always include dependencies, even if empty:
# /// script
# requires-python = ">=3.11"
# dependencies = []
# ///
Problem: Unlike UV projects, scripts don't auto-generate lockfiles.
Solution: Explicitly lock if reproducibility needed:
uv lock --script script.py
This creates script.lock alongside script.py.
Problem: Standard shebang won't work with multi-word commands.
#!/usr/bin/env uv run --script
# ERROR: env can't handle multiple arguments
Solution: Use -S flag:
#!/usr/bin/env -S uv run --script
# SUCCESS: Splits arguments correctly
Before finalizing a UV script:
Format (official requirements):
# /// script comment blockdependencies field present (even if empty)# ///Best practices:
-S flag for executableschmod +x if intended for direct executionTemplate for hook scripts:
#!/usr/bin/env -S uv run --script
# /// script
# dependencies = [
# "tool-name>=1.0.0",
# ]
# ///
import subprocess
import sys
import os
def main():
"""Hook logic here."""
# Get file paths from environment
file_paths = os.environ.get("CLAUDE_FILE_PATHS", "").split()
if not file_paths:
sys.exit(0) # Nothing to process
# Run tool
result = subprocess.run(
["tool-name", *file_paths],
capture_output=True,
text=True
)
if result.returncode != 0:
print(result.stderr, file=sys.stderr)
sys.exit(2) # Block operation
sys.exit(0) # Success
if __name__ == "__main__":
main()
Hook registration in hooks.json:
{
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "./hooks/format_code.py"
}
]
}
]
}
Use underscores, not hyphens, in Python script names.
✅ format_code.py (importable, testable)
✅ lint_on_write.py (importable, testable)
❌ format-code.py (cannot import - hyphen is minus operator)
❌ lint-on-write.py (cannot import - hyphen is minus operator)
Why this matters: Hyphenated Python files require importlib.util workarounds for testing. Using underscores allows normal import statements and pytest discovery.
Note: Shell scripts (.sh) can use hyphens freely - this only affects Python files.
Official UV documentation:
Related patterns: