A lightweight framework for implementing Claude Code hooks in Python.
Project description
Claude PyHooks
A lightweight framework for implementing Claude Code hooks in Python.
Overview
Claude PyHooks makes it easy to build and register custom hooks for Claude Code.
The following hook types are supported:
| Hook type | Typical use cases |
|---|---|
| PreToolUse | Validate or block tool calls (e.g. dangerous shell commands) |
| PostToolUse | Post-processing after tool execution (logging, backups, notifications, …) |
| Notification | Handle general notifications emitted by Claude Code |
| Stop | Run cleanup logic when a session ends |
| SubagentStop | Run cleanup logic when a sub-agent terminates |
Installation
pip install claude-pyhooks
# or, if you use uv
uv add claude-pyhooks
Development version (recommended with uv)
git clone <this-repository>
cd claude-pyhooks
uv sync
uv pip install -e .
Quick start
PreToolUse — block a dangerous command
#!/usr/bin/env python3
from claude_pyhooks import (
PreToolUseInput,
PreToolUseOutput,
BashInput,
)
def main() -> None:
hook_input = PreToolUseInput.from_stdin()
if hook_input.tool_name == "Bash":
assert isinstance(hook_input.tool_input, BashInput)
command = (hook_input.tool_input.command or "").strip()
if "rm -rf /" in command:
PreToolUseOutput.block(
f"Dangerous command blocked: {command}"
).execute()
if __name__ == "__main__":
main()
PostToolUse — log every Bash run
#!/usr/bin/env python3
from claude_pyhooks import (
PreToolUseInput,
PreToolUseOutput,
BashInput,
)
def main() -> None:
hook_input = PreToolUseInput.from_stdin()
if hook_input.tool_name == "Bash":
assert isinstance(hook_input.tool_input, BashInput)
command = (hook_input.tool_input.command or "").strip()
if "rm -rf /" in command:
PreToolUseOutput.block(
f"Dangerous command blocked: {command}"
).execute()
if __name__ == "__main__":
main()
Notification — play a beep
#!/usr/bin/env python3
from pathlib import Path
from claude_pyhooks import (
PostToolUseInput,
BashInput,
BashResponse,
)
log_file = Path(__file__).with_name("bash_command_log.txt")
def main() -> None:
hook_input = PostToolUseInput.from_stdin()
if hook_input.tool_name == "Bash":
assert isinstance(hook_input.tool_input, BashInput)
assert isinstance(hook_input.tool_response, BashResponse)
command = hook_input.tool_input.command
success = not hook_input.tool_response.stderr
status = "success" if success else "failure"
with log_file.open("a") as f:
f.write(f"{status}: {command}\n")
if __name__ == "__main__":
main()
Registering hooks with Claude Code
Add the hooks to ~/.claude/settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "uv run /path/to/pre_hook.py"
}
]
}
]
}
}
API highlights
Input classes
| Class | Description |
|---|---|
PreToolUseInput |
Data passed before a tool runs |
PostToolUseInput |
Data passed after a tool runs |
NotificationInput |
Generic notification payload |
StopInput |
Session-end payload |
SubagentStopInput |
Sub-agent end payload |
tool_input and tool_response are automatically parsed into strongly-typed objects such as BashInput or BashResponse.
Output classes
| Class | Description |
|---|---|
PreToolUseOutput |
Approve or block execution (approve(), block()) |
PostToolUseOutput |
Return post-processing commands |
StopOutput |
Commands on session end |
SubagentStopOutput |
Commands on sub-agent end |
Calling .execute() dispatches the command queue.
If the output blocks execution, the process exits with code 2 automatically.
Beep utilities
Pre-defined constants:
DEFAULT_BEEPSUCCESS_BEEPERROR_BEEPWARNING_BEEP
Each constant is an instance of BeepCommand and can be part of a BeepSequence.
Examples
See the examples/ directory for practical scripts:
| File | What it does |
|---|---|
pre_bash.py |
Blocks dangerous Bash commands |
post_bash.py |
Logs Bash command results |
notification_beep.py |
Plays beeps based on notification |
License
MIT License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file claude_pyhooks-0.1.0.tar.gz.
File metadata
- Download URL: claude_pyhooks-0.1.0.tar.gz
- Upload date:
- Size: 26.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
36b22f516057dcd29cb23bd4d689a6cb9cd7b1d17a6e99223f8b278b83637f06
|
|
| MD5 |
f3ec3f146c655d0da5d65aecac039a73
|
|
| BLAKE2b-256 |
e9cf0acf08655f42d91b25d42efadda6baa06115cecf50b8f7e01d79d4dba9a1
|
File details
Details for the file claude_pyhooks-0.1.0-py3-none-any.whl.
File metadata
- Download URL: claude_pyhooks-0.1.0-py3-none-any.whl
- Upload date:
- Size: 17.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ffd2408b4261b92323dde36b3650e145a3278ccf03d8ca8b2de2cd4223522eff
|
|
| MD5 |
9ed3fce1ef2cccd757fe81539833f4b2
|
|
| BLAKE2b-256 |
8d642d15ea67670522ea2512326699d4bf35717f34675f5e6d179f5847733868
|