From 84edd730417ac6de72e3ce408fa81952ebc7d14c Mon Sep 17 00:00:00 2001 From: Dickson Tsai Date: Thu, 20 Nov 2025 06:50:33 +0900 Subject: [PATCH] feat: add timeout parameter to HookMatcher (#351) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Adds optional `timeout` field to `HookMatcher` dataclass in `types.py` that allows users to specify a custom timeout (in seconds) for hooks - Propagates the timeout value through: - `client.py` and `_internal/client.py`: `_convert_hooks_to_internal()` method - `_internal/query.py`: hook config sent to CLI ## Test plan - [x] Verify hooks work without timeout specified (default behavior) - [x] Verify custom timeout is passed to CLI when specified in HookMatcher 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude --- src/claude_agent_sdk/_internal/client.py | 4 +++- src/claude_agent_sdk/_internal/query.py | 13 +++++++------ src/claude_agent_sdk/client.py | 4 +++- src/claude_agent_sdk/types.py | 3 +++ 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/claude_agent_sdk/_internal/client.py b/src/claude_agent_sdk/_internal/client.py index 6dbc877..5246627 100644 --- a/src/claude_agent_sdk/_internal/client.py +++ b/src/claude_agent_sdk/_internal/client.py @@ -31,10 +31,12 @@ class InternalClient: internal_hooks[event] = [] for matcher in matchers: # Convert HookMatcher to internal dict format - internal_matcher = { + internal_matcher: dict[str, Any] = { "matcher": matcher.matcher if hasattr(matcher, "matcher") else None, "hooks": matcher.hooks if hasattr(matcher, "hooks") else [], } + if hasattr(matcher, "timeout") and matcher.timeout is not None: + internal_matcher["timeout"] = matcher.timeout internal_hooks[event].append(internal_matcher) return internal_hooks diff --git a/src/claude_agent_sdk/_internal/query.py b/src/claude_agent_sdk/_internal/query.py index 7646010..b76bc04 100644 --- a/src/claude_agent_sdk/_internal/query.py +++ b/src/claude_agent_sdk/_internal/query.py @@ -126,12 +126,13 @@ class Query: self.next_callback_id += 1 self.hook_callbacks[callback_id] = callback callback_ids.append(callback_id) - hooks_config[event].append( - { - "matcher": matcher.get("matcher"), - "hookCallbackIds": callback_ids, - } - ) + hook_matcher_config: dict[str, Any] = { + "matcher": matcher.get("matcher"), + "hookCallbackIds": callback_ids, + } + if matcher.get("timeout") is not None: + hook_matcher_config["timeout"] = matcher.get("timeout") + hooks_config[event].append(hook_matcher_config) # Send initialize request request = { diff --git a/src/claude_agent_sdk/client.py b/src/claude_agent_sdk/client.py index f95b50b..58b851d 100644 --- a/src/claude_agent_sdk/client.py +++ b/src/claude_agent_sdk/client.py @@ -75,10 +75,12 @@ class ClaudeSDKClient: internal_hooks[event] = [] for matcher in matchers: # Convert HookMatcher to internal dict format - internal_matcher = { + internal_matcher: dict[str, Any] = { "matcher": matcher.matcher if hasattr(matcher, "matcher") else None, "hooks": matcher.hooks if hasattr(matcher, "hooks") else [], } + if hasattr(matcher, "timeout") and matcher.timeout is not None: + internal_matcher["timeout"] = matcher.timeout internal_hooks[event].append(internal_matcher) return internal_hooks diff --git a/src/claude_agent_sdk/types.py b/src/claude_agent_sdk/types.py index 406c204..689d8bb 100644 --- a/src/claude_agent_sdk/types.py +++ b/src/claude_agent_sdk/types.py @@ -366,6 +366,9 @@ class HookMatcher: # A list of Python functions with function signature HookCallback hooks: list[HookCallback] = field(default_factory=list) + # Timeout in seconds for all hooks in this matcher (default: 60) + timeout: int | None = None + # MCP Server config class McpStdioServerConfig(TypedDict):