Skip to content

Commit a0e70aa

Browse files
husniadilclaude
andcommitted
feat(types): add PostCompact hook event support
Add PostCompact hook event type, input, and specific output types to match Claude Code CLI v2.1.76+. PostCompact fires after compact operations complete, providing trigger type and compact summary. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 971994c commit a0e70aa

File tree

4 files changed

+118
-0
lines changed

4 files changed

+118
-0
lines changed

src/claude_agent_sdk/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@
5050
PermissionResultAllow,
5151
PermissionResultDeny,
5252
PermissionUpdate,
53+
PostCompactHookInput,
54+
PostCompactHookSpecificOutput,
5355
PostToolUseFailureHookInput,
5456
PostToolUseFailureHookSpecificOutput,
5557
PostToolUseHookInput,
@@ -403,6 +405,8 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> Any:
403405
"StopHookInput",
404406
"SubagentStopHookInput",
405407
"PreCompactHookInput",
408+
"PostCompactHookInput",
409+
"PostCompactHookSpecificOutput",
406410
"NotificationHookInput",
407411
"SubagentStartHookInput",
408412
"PermissionRequestHookInput",

src/claude_agent_sdk/types.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ class PermissionResultDeny:
170170
| Literal["Stop"]
171171
| Literal["SubagentStop"]
172172
| Literal["PreCompact"]
173+
| Literal["PostCompact"]
173174
| Literal["Notification"]
174175
| Literal["SubagentStart"]
175176
| Literal["PermissionRequest"]
@@ -273,6 +274,14 @@ class PreCompactHookInput(BaseHookInput):
273274
custom_instructions: str | None
274275

275276

277+
class PostCompactHookInput(BaseHookInput):
278+
"""Input data for PostCompact hook events."""
279+
280+
hook_event_name: Literal["PostCompact"]
281+
trigger: Literal["manual", "auto"]
282+
compact_summary: str
283+
284+
276285
class NotificationHookInput(BaseHookInput):
277286
"""Input data for Notification hook events."""
278287

@@ -308,6 +317,7 @@ class PermissionRequestHookInput(BaseHookInput, _SubagentContextMixin):
308317
| StopHookInput
309318
| SubagentStopHookInput
310319
| PreCompactHookInput
320+
| PostCompactHookInput
311321
| NotificationHookInput
312322
| SubagentStartHookInput
313323
| PermissionRequestHookInput
@@ -354,6 +364,13 @@ class SessionStartHookSpecificOutput(TypedDict):
354364
additionalContext: NotRequired[str]
355365

356366

367+
class PostCompactHookSpecificOutput(TypedDict):
368+
"""Hook-specific output for PostCompact events."""
369+
370+
hookEventName: Literal["PostCompact"]
371+
additionalContext: NotRequired[str]
372+
373+
357374
class NotificationHookSpecificOutput(TypedDict):
358375
"""Hook-specific output for Notification events."""
359376

@@ -381,6 +398,7 @@ class PermissionRequestHookSpecificOutput(TypedDict):
381398
| PostToolUseFailureHookSpecificOutput
382399
| UserPromptSubmitHookSpecificOutput
383400
| SessionStartHookSpecificOutput
401+
| PostCompactHookSpecificOutput
384402
| NotificationHookSpecificOutput
385403
| SubagentStartHookSpecificOutput
386404
| PermissionRequestHookSpecificOutput

tests/test_tool_callbacks.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -744,6 +744,63 @@ async def pre_tool_hook(
744744
)
745745
assert result["hookSpecificOutput"]["permissionDecision"] == "allow"
746746

747+
@pytest.mark.asyncio
748+
async def test_post_compact_hook_callback(self):
749+
"""Test that a PostCompact hook callback receives correct input and returns output."""
750+
hook_calls: list[dict[str, Any]] = []
751+
752+
async def post_compact_hook(
753+
input_data: HookInput, tool_use_id: str | None, context: HookContext
754+
) -> HookJSONOutput:
755+
hook_calls.append({"input": input_data, "tool_use_id": tool_use_id})
756+
return {
757+
"hookSpecificOutput": {
758+
"hookEventName": "PostCompact",
759+
"additionalContext": "Compaction logged successfully",
760+
}
761+
}
762+
763+
transport = MockTransport()
764+
query = Query(
765+
transport=transport, is_streaming_mode=True, can_use_tool=None, hooks={}
766+
)
767+
768+
callback_id = "test_post_compact_hook"
769+
query.hook_callbacks[callback_id] = post_compact_hook
770+
771+
request = {
772+
"type": "control_request",
773+
"request_id": "test-post-compact",
774+
"request": {
775+
"subtype": "hook_callback",
776+
"callback_id": callback_id,
777+
"input": {
778+
"session_id": "sess-1",
779+
"transcript_path": "/tmp/t",
780+
"cwd": "/home",
781+
"hook_event_name": "PostCompact",
782+
"trigger": "auto",
783+
"compact_summary": "Conversation summary...",
784+
},
785+
"tool_use_id": None,
786+
},
787+
}
788+
789+
await query._handle_control_request(request)
790+
791+
assert len(hook_calls) == 1
792+
assert hook_calls[0]["input"]["hook_event_name"] == "PostCompact"
793+
assert hook_calls[0]["input"]["trigger"] == "auto"
794+
assert hook_calls[0]["input"]["compact_summary"] == "Conversation summary..."
795+
796+
response_data = json.loads(transport.written_messages[-1])
797+
result = response_data["response"]["response"]
798+
assert result["hookSpecificOutput"]["hookEventName"] == "PostCompact"
799+
assert (
800+
result["hookSpecificOutput"]["additionalContext"]
801+
== "Compaction logged successfully"
802+
)
803+
747804

748805
class TestHookInitializeRegistration:
749806
"""Test that new hook events can be registered through the initialize flow."""

tests/test_types.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
NotificationHookSpecificOutput,
88
PermissionRequestHookInput,
99
PermissionRequestHookSpecificOutput,
10+
PostCompactHookInput,
11+
PostCompactHookSpecificOutput,
1012
ResultMessage,
1113
SubagentStartHookInput,
1214
SubagentStartHookSpecificOutput,
@@ -286,6 +288,34 @@ def test_permission_request_hook_input_with_suggestions(self):
286288
}
287289
assert len(hook_input["permission_suggestions"]) == 1
288290

291+
def test_post_compact_hook_input(self):
292+
"""Test PostCompactHookInput construction."""
293+
hook_input: PostCompactHookInput = {
294+
"session_id": "sess-1",
295+
"transcript_path": "/tmp/transcript.jsonl",
296+
"cwd": "/home/user/project",
297+
"hook_event_name": "PostCompact",
298+
"trigger": "auto",
299+
"compact_summary": "Summary of the compacted conversation...",
300+
}
301+
assert hook_input["hook_event_name"] == "PostCompact"
302+
assert hook_input["trigger"] == "auto"
303+
assert (
304+
hook_input["compact_summary"] == "Summary of the compacted conversation..."
305+
)
306+
307+
def test_post_compact_hook_input_manual_trigger(self):
308+
"""Test PostCompactHookInput with manual trigger."""
309+
hook_input: PostCompactHookInput = {
310+
"session_id": "sess-2",
311+
"transcript_path": "/tmp/transcript.jsonl",
312+
"cwd": "/home/user/project",
313+
"hook_event_name": "PostCompact",
314+
"trigger": "manual",
315+
"compact_summary": "User-initiated compaction summary",
316+
}
317+
assert hook_input["trigger"] == "manual"
318+
289319

290320
class TestHookSpecificOutputTypes:
291321
"""Test hook-specific output type definitions."""
@@ -332,6 +362,15 @@ def test_post_tool_use_output_has_updated_mcp_tool_output(self):
332362
}
333363
assert output["updatedMCPToolOutput"] == {"result": "modified"}
334364

365+
def test_post_compact_hook_specific_output(self):
366+
"""Test PostCompactHookSpecificOutput construction."""
367+
output: PostCompactHookSpecificOutput = {
368+
"hookEventName": "PostCompact",
369+
"additionalContext": "Compaction complete, summary logged",
370+
}
371+
assert output["hookEventName"] == "PostCompact"
372+
assert output["additionalContext"] == "Compaction complete, summary logged"
373+
335374

336375
class TestMcpServerStatusTypes:
337376
"""Test MCP server status type definitions."""

0 commit comments

Comments
 (0)