Skip to content

Commit c996651

Browse files
committed
refactor: make HookEventMessage a SystemMessage subclass
HookEventMessage arrives on the wire as a system-typed message (type=system, subtype=hook_started|hook_response), matching the established pattern for TaskStartedMessage, TaskProgressMessage, TaskNotificationMessage, and MirrorErrorMessage. Subclassing SystemMessage means existing isinstance(msg, SystemMessage) and case SystemMessage() checks continue to match, and the redundant HookEventMessage entry can be dropped from the Message union. Also fix the docstring to reference the actual hook_response keys (output, exit_code, outcome) instead of a nonexistent response key, and update the test fixture to match the real wire shape.
1 parent 6164908 commit c996651

2 files changed

Lines changed: 21 additions & 8 deletions

File tree

src/claude_agent_sdk/types.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1153,7 +1153,7 @@ class RateLimitEvent:
11531153

11541154

11551155
@dataclass
1156-
class HookEventMessage:
1156+
class HookEventMessage(SystemMessage):
11571157
"""Hook event emitted by the CLI when ``include_hook_events`` is enabled.
11581158
11591159
When ``ClaudeAgentOptions.include_hook_events`` is ``True``, the CLI emits
@@ -1164,10 +1164,15 @@ class HookEventMessage:
11641164
These arrive on the wire as ``{"type": "system", "subtype":
11651165
"hook_started" | "hook_response", "hook_event": "PreToolUse", ...}``.
11661166
1167+
Subclass of SystemMessage: existing ``isinstance(msg, SystemMessage)`` and
1168+
``case SystemMessage()`` checks continue to match. The base ``subtype``
1169+
and ``data`` fields remain populated with the raw payload.
1170+
11671171
Attributes:
11681172
subtype: Lifecycle phase — ``"hook_started"`` when a hook begins
11691173
executing, ``"hook_response"`` when it completes (the latter
1170-
carries a ``response`` key in ``data``).
1174+
carries ``output``, ``exit_code``, and ``outcome`` keys in
1175+
``data``).
11711176
hook_event_name: Name of the hook event (e.g. ``"PreToolUse"``,
11721177
``"PostToolUse"``, ``"Stop"``).
11731178
data: Full raw event dict from the CLI, including any
@@ -1176,9 +1181,7 @@ class HookEventMessage:
11761181
uuid: Unique ID of the event, if present.
11771182
"""
11781183

1179-
subtype: str
1180-
hook_event_name: str
1181-
data: dict[str, Any] = field(default_factory=dict)
1184+
hook_event_name: str = ""
11821185
session_id: str | None = None
11831186
uuid: str | None = None
11841187

@@ -1190,7 +1193,6 @@ class HookEventMessage:
11901193
| ResultMessage
11911194
| StreamEvent
11921195
| RateLimitEvent
1193-
| HookEventMessage
11941196
)
11951197

11961198

tests/test_message_parser.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -995,15 +995,26 @@ def test_parse_hook_event_message_response(self):
995995
"hook_name": "PostToolUse",
996996
"session_id": "sess-123",
997997
"uuid": "uuid-789",
998-
"response": {"decision": "approve"},
998+
"output": "",
999+
"exit_code": 0,
1000+
"outcome": "success",
9991001
}
10001002
message = parse_message(data)
10011003
assert isinstance(message, HookEventMessage)
10021004
assert message.subtype == "hook_response"
10031005
assert message.hook_event_name == "PostToolUse"
10041006
assert message.session_id == "sess-123"
10051007
assert message.uuid == "uuid-789"
1006-
assert message.data["response"] == {"decision": "approve"}
1008+
assert message.data["output"] == ""
1009+
assert message.data["exit_code"] == 0
1010+
assert message.data["outcome"] == "success"
1011+
1012+
def test_parse_hook_event_message_isinstance_system(self):
1013+
"""HookEventMessage is a SystemMessage subclass for backward compat."""
1014+
data = {"type": "system", "subtype": "hook_started", "hook_event": "PreToolUse"}
1015+
message = parse_message(data)
1016+
assert isinstance(message, HookEventMessage)
1017+
assert isinstance(message, SystemMessage)
10071018

10081019
def test_parse_hook_event_message_minimal(self):
10091020
"""Hook events without session_id/uuid/hook_event still parse."""

0 commit comments

Comments
 (0)