Skip to content

Hooks not triggering in Claude Agent SDK #213

@smbzhang

Description

@smbzhang

Description:
I'm attempting to use hooks in the claude_agent_sdk to validate and log tool usage, but the hooks don't seem to be triggered at all. The code is based on the official example provided in the documentation: .

https://docs.claude.com/en/api/agent-sdk/python#hook-usage-example

Code Example:

from claude_agent_sdk import query, ClaudeAgentOptions, HookMatcher, HookContext
from typing import Any
import asyncio

import env_setup

env_setup.setup_anthropic_env()

async def validate_bash_command(
    input_data: dict[str, Any],
    tool_use_id: str | None,
    context: HookContext
) -> dict[str, Any]:
    """Validate and potentially block dangerous bash commands."""
    if input_data['tool_name'] == 'Bash':
        command = input_data['tool_input'].get('command', '')
        if 'rm -rf /' in command:
            return {
                'hookSpecificOutput': {
                    'hookEventName': 'PreToolUse',
                    'permissionDecision': 'deny',
                    'permissionDecisionReason': 'Dangerous command blocked'
                }
            }
    return {}

async def log_tool_use(
    input_data: dict[str, Any],
    tool_use_id: str | None,
    context: HookContext
) -> dict[str, Any]:
    """Log all tool usage for auditing."""
    print(f"Tool used: {input_data.get('tool_name')}")
    return {}
async def main():
    options = ClaudeAgentOptions(
        hooks={
            'PreToolUse': [
                HookMatcher(matcher='Bash', hooks=[validate_bash_command]),
                HookMatcher(hooks=[log_tool_use])  # Applies to all tools
            ],
            'PostToolUse': [
                HookMatcher(hooks=[log_tool_use])
            ]
        }
    )

    async for message in query(
        prompt="Analyze this codebase",
        options=options
    ):
        print(message)

asyncio.run(main())

Expected Behavior:

validate_bash_command should run before executing the Bash tool.

log_tool_use should run before and after any tool usage.

Actual Behavior:

Hooks are not executed at all. No logging appears, and dangerous commands are not blocked.

Environment:

Python version: [e.g., 3.11]

claude_agent_sdk version: [0.1.1 & 0.1.0]

OS: [macOS]

Program some OUTPUT:

SystemMessage(subtype='init', data={'type': 'system', 'subtype': 'init', 'cwd': '/Users/zhangyongqiang01/Desktop/code/test/python/claude-code-sdk', 'session_id': '158badd7-7b61-412f-8cac-d7c3a6be3952', 'tools': ['Task', 'Bash', 'Glob', 'Grep', 'ExitPlanMode', 'Read', 'Edit', 'Write', 'NotebookEdit', 'WebFetch', 'TodoWrite', 'WebSearch', 'BashOutput', 'KillShell', 'SlashCommand'], 'mcp_servers': [], 'model': 'k2', 'permissionMode': 'default', 'slash_commands': ['compact', 'context', 'cost', 'init', 'output-style:new', 'pr-comments', 'release-notes', 'todos', 'review', 'security-review'], 'apiKeySource': 'none', 'output_style': 'default', 'agents': ['general-purpose', 'statusline-setup', 'output-style-setup'], 'uuid': 'ca7ce821-afb2-48e0-8a67-f32879155df2'})
AssistantMessage(content=[TextBlock(text="I'll analyze the codebase for you. Let me start by exploring the directory structure and understanding what this project contains.")], model='k2', parent_tool_use_id=None)
AssistantMessage(content=[ToolUseBlock(id='functions.TodoWrite:0', name='TodoWrite', input={'todos': [{'id': '1', 'task': 'Explore directory structure and identify project type', 'status': 'pending'}, {'id': '2', 'task': 'Analyze key configuration files (package.json, requirements.txt, etc.)', 'status': 'pending'}, {'id': '3', 'task': 'Examine source code structure and main components', 'status': 'pending'}, {'id': '4', 'task': 'Identify dependencies and frameworks used', 'status': 'pending'}, {'id': '5', 'task': 'Summarize findings and project characteristics', 'status': 'pending'}]})], model='k2', parent_tool_use_id=None)
UserMessage(content=[ToolResultBlock(tool_use_id='functions.TodoWrite:0', content='<tool_use_error>InputValidationError: TodoWrite failed due to the following issues:\nThe required parameter `todos[0].content` is missing\nThe required parameter `todos[0].activeForm` is missing\nThe required parameter `todos[1].content` is missing\nThe required parameter `todos[1].activeForm` is missing\nThe required parameter `todos[2].content` is missing\nThe required parameter `todos[2].activeForm` is missing\nThe required parameter `todos[3].content` is missing\nThe required parameter `todos[3].activeForm` is missing\nThe required parameter `todos[4].content` is missing\nThe required parameter `todos[4].activeForm` is missing</tool_use_error>', is_error=True)], parent_tool_use_id=None)
AssistantMessage(content=[ToolUseBlock(id='functions.TodoWrite:1', name='TodoWrite', input={'todos': [{'id': '1', 'task': 'Explore directory structure and identify project type', 'content': 'Explore directory structure and identify project type', 'activeForm': 'text'}, {'id': '2', 'task': 'Analyze key configuration files (package.json, requirements.txt, etc.)', 'content': 'Analyze key configuration files (package.json, requirements.txt, etc.)', 'activeForm': 'text'}, {'id': '3', 'task': 'Examine source code structure and main components', 'content': 'Examine source code structure and main components', 'activeForm': 'text'}, {'id': '4', 'task': 'Identify dependencies and frameworks used', 'content': 'Identify dependencies and frameworks used', 'activeForm': 'text'}, {'id': '5', 'task': 'Summarize findings and project characteristics', 'content': 'Summarize findings and project characteristics', 'activeForm': 'text'}]})], model='k2', parent_tool_use_id=None)
UserMessage(content=[ToolResultBlock(tool_use_id='functions.TodoWrite:1', content='<tool_use_error>InputValidationError: TodoWrite failed due to the following issues:\nThe required parameter `todos[0].status` is missing\nThe required parameter `todos[1].status` is missing\nThe required parameter `todos[2].status` is missing\nThe required parameter `todos[3].status` is missing\nThe required parameter `todos[4].status` is missing</tool_use_error>', is_error=True)], parent_tool_use_id=None)
AssistantMessage(content=[ToolUseBlock(id='functions.TodoWrite:2', name='TodoWrite', input={'todos': [{'id': '1', 'task': 'Explore directory structure and identify project type', 'content': 'Explore directory structure and identify project type', 'activeForm': 'text', 'status': 'pending'}, {'id': '2', 'task': 'Analyze key configuration files (package.json, requirements.txt, etc.)', 'content': 'Analyze key configuration files (package.json, requirements.txt, etc.)', 'activeForm': 'text', 'status': 'pending'}, {'id': '3', 'task': 'Examine source code structure and main components', 'content': 'Examine source code structure and main components', 'activeForm': 'text', 'status': 'pending'}, {'id': '4', 'task': 'Identify dependencies and frameworks used', 'content': 'Identify dependencies and frameworks used', 'activeForm': 'text', 'status': 'pending'}, {'id': '5', 'task': 'Summarize findings and project characteristics', 'content': 'Summarize findings and project characteristics', 'activeForm': 'text', 'status': 'pending'}]})], model='k2', parent_tool_use_id=None)
UserMessage(content=[ToolResultBlock(tool_use_id='functions.TodoWrite:2', content='Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable', is_error=None)], parent_tool_use_id=None)
AssistantMessage(content=[ToolUseBlock(id='functions.TodoWrite:3', name='TodoWrite', input={'todos': [{'id': '1', 'task': 'Explore directory structure and identify project type', 'content': 'Explore directory structure and identify project type', 'activeForm': 'text', 'status': 'in_progress'}, {'id': '2', 'task': 'Analyze key configuration files (package.json, requirements.txt, etc.)', 'content': 'Analyze key configuration files (package.json, requirements.txt, etc.)', 'activeForm': 'text', 'status': 'pending'}, {'id': '3', 'task': 'Examine source code structure and main components', 'content': 'Examine source code structure and main components', 'activeForm': 'text', 'status': 'pending'}, {'id': '4', 'task': 'Identify dependencies and frameworks used', 'content': 'Identify dependencies and frameworks used', 'activeForm': 'text', 'status': 'pending'}, {'id': '5', 'task': 'Summarize findings and project characteristics', 'content': 'Summarize findings and project characteristics', 'activeForm': 'text', 'status': 'pending'}]})], model='k2', parent_tool_use_id=None)
UserMessage(content=[ToolResultBlock(tool_use_id='functions.TodoWrite:3', content='Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable', is_error=None)], parent_tool_use_id=None)
AssistantMessage(content=[ToolUseBlock(id='functions.LS:4', name='LS', input={'path': '/Users/zhangyongqiang01/Desktop/code/test/python/claude-code-sdk'})], model='k2', parent_tool_use_id=None)
UserMessage(content=[ToolResultBlock(tool_use_id='functions.LS:4', content='<tool_use_error>Error: No such tool available: LS</tool_use_error>', is_error=True)], parent_tool_use_id=None)
AssistantMessage(content=[ToolUseBlock(id='functions.Bash:5', name='Bash', input={'command': 'ls -la /Users/zhangyongqiang01/Desktop/code/test/python/claude-code-sdk'})], model='k2', parent_tool_use_id=None)
UserMessage(content=[ToolResultBlock(tool_use_id='functions.Bash:5', content='total 96\ndrwxr-xr-x  17 zhangyongqiang01  staff   544 10  7 14:49 .\ndrwxr-xr-x   3 zhangyongqiang01  staff    96  9 28 20:02 ..\ndrwxr-xr-x   4 zhangyongqiang01  staff   128  9 29 16:33 .vscode\ndrwxr-xr-x   3 zhangyongqiang01  staff    96  9 29 17:41 __pycache__\n-rw-r--r--   1 zhangyongqiang01  staff  1432 10  7 13:48 advanced_permission_control.py\n-rw-r--r--   1 zhangyongqiang01  staff   432  9 29 19:43 claude_sdk_client.py\n-rw-r--r--   1 zhangyongqiang01  staff  1449  9 29 19:46 continue_conversation.py\n-rw-r--r--   1 zhangyongqiang01  staff   418  9 29 10:52 env_setup.py\n-rw-r--r--   1 zhangyongqiang01  staff  1562 10  7 15:22 hook_use_example.py\n-rw-r--r--   1 zhangyongqiang01  staff   783  9 29 10:47 mcp_server.py\n-rw-r--r--   1 zhangyongqiang01  staff   530  9 29 10:53 query.py\ndrwxr-xr-x   3 zhangyongqiang01  staff    96  9 28 20:20 runtime\n-rw-r--r--   1 zhangyongqiang01  staff  3324  9 30 15:58 streaming_input.py\n-rw-r--r--   1 zhangyongqiang01  staff  3939  9 29 16:39 test_asyncio.py\n-rw-r--r--   1 zhangyongqiang01  staff  1518  9 29 17:22 test_asyncio_2.py\n-rw-r--r--   1 zhangyongqiang01  staff   285  9 29 16:30 tool.py\n-rw-r--r--   1 zhangyongqiang01  staff  1193 10  7 13:45 use_interrupts.py', is_error=False)], parent_tool_use_id=None)
AssistantMessage(content=[ToolUseBlock(id='functions.Bash:6', name='Bash', input={'command': 'find /Users/zhangyongqiang01/Desktop/code/test/python/claude-code-sdk -name "*.py" | head -10'})], model='k2', parent_tool_use_id=None)
UserMessage(content=[ToolResultBlock(tool_use_id='functions.Bash:6', content='/Users/zhangyongqiang01/Desktop/code/test/python/claude-code-sdk/use_interrupts.py\n/Users/zhangyongqiang01/Desktop/code/test/python/claude-code-sdk/test_asyncio_2.py\n/Users/zhangyongqiang01/Desktop/code/test/python/claude-code-sdk/query.py\n/Users/zhangyongqiang01/Desktop/code/test/python/claude-code-sdk/runtime/web_server.py\n/Users/zhangyongqiang01/Desktop/code/test/python/claude-code-sdk/env_setup.py\n/Users/zhangyongqiang01/Desktop/code/test/python/claude-code-sdk/test_asyncio.py\n/Users/zhangyongqiang01/Desktop/code/test/python/claude-code-sdk/continue_conversation.py\n/Users/zhangyongqiang01/Desktop/code/test/python/claude-code-sdk/streaming_input.py\n/Users/zhangyongqiang01/Desktop/code/test/python/claude-code-sdk/hook_use_example.py\n/Users/zhangyongqiang01/Desktop/code/test/python/claude-code-sdk/tool.py', is_error=False)], parent_tool_use_id=None)
AssistantMessage(content=[ToolUseBlock(id='functions.Bash:7', name='Bash', input={'command': 'find /Users/zhangyongqiang01/Desktop/code/test/python/claude-code-sdk -name "requirements.txt" -o -name "setup.py" -o -name "pyproject.toml" -o -name "Pipfile" -o -name "environment.yml"'})], model='k2', parent_tool_use_id=None)
UserMessage(content=[ToolResultBlock(tool_use_id='functions.Bash:7', content='', is_error=False)], parent_tool_use_id=None)
AssistantMessage(content=[ToolUseBlock(id='functions.Read:8', name='Read', input={'path': '/Users/zhangyongqiang01/Desktop/code/test/python/claude-code-sdk/claude_sdk_client.py'})], model='k2', parent_tool_use_id=None)
UserMessage(content=[ToolResultBlock(tool_use_id='functions.Read:8', content='<tool_use_error>InputValidationError: Read failed due to the following issues:\nThe required parameter `file_path` is missing\nAn unexpected parameter `path` was provided</tool_use_error>', is_error=True)], parent_tool_use_id=None)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingduplicateThis issue or pull request already exists

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions