Tutorials · Claude Agent SDK · examples / python / 05_hooks.pyExample code

Example code · python

05_hooks.py

Hooks. PreToolUse blocks writes to .env files; PostToolUse logs every tool result.

Raw fileDownload all examples (zip)See its output →

Used in Part 2 · Example: protect .env, log everything

"""05: Hooks. PreToolUse blocks writes to .env files; PostToolUse logs every tool result."""

import asyncio

from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient, HookMatcher, ResultMessage, TextBlock, AssistantMessage, UserMessage, ToolResultBlock
from demo_util import PROJECT

audit_log = []


async def protect_env_files(input_data, tool_use_id, context):
    file_path = input_data["tool_input"].get("file_path", "")
    if file_path.split("/")[-1] in (".env", ".env.local"):
        return {
            "hookSpecificOutput": {
                "hookEventName": "PreToolUse",
                "permissionDecision": "deny",
                "permissionDecisionReason": "Policy: .env files may not be created or modified by the agent",
            }
        }
    return {}  # empty dict = no opinion, let the normal permission flow decide


async def audit(input_data, tool_use_id, context):
    audit_log.append((input_data["tool_name"], tool_use_id))
    return {}


async def main():
    options = ClaudeAgentOptions(
        cwd=PROJECT,
        permission_mode="acceptEdits",
        allowed_tools=["Read", "Write", "Edit", "Glob"],
        max_turns=6,
        hooks={
            "PreToolUse": [HookMatcher(matcher="Write|Edit", hooks=[protect_env_files])],
            "PostToolUse": [HookMatcher(hooks=[audit])],  # no matcher: every tool
        },
    )
    async with ClaudeSDKClient(options=options) as client:
        await client.query("Create a .env file containing DATABASE_URL=sqlite:///users.db, "
                           "then create config.example.txt with the same line.")
        async for message in client.receive_response():
            if isinstance(message, AssistantMessage):
                for block in message.content:
                    if isinstance(block, TextBlock):
                        print(f"Claude: {block.text}")
            elif isinstance(message, UserMessage) and isinstance(message.content, list):
                for block in message.content:
                    if isinstance(block, ToolResultBlock):
                        print(f"[tool_result]{' ERROR' if block.is_error else ''} {str(block.content)[:200]}")
            elif isinstance(message, ResultMessage):
                print(f"[result] {message.subtype}, denials={message.permission_denials}")

    print("\nAudit log (PostToolUse):", audit_log)


asyncio.run(main())

59 lines · Back to the folder