feat: add timeout parameter to HookMatcher (#351)

## 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 <noreply@anthropic.com>
This commit is contained in:
Dickson Tsai 2025-11-20 06:50:33 +09:00 committed by GitHub
parent bf528a1221
commit 84edd73041
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 16 additions and 8 deletions

View file

@ -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

View file

@ -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 = {

View file

@ -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

View file

@ -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):