Skip to content

Commit 8f12409

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 b63e8f1 commit 8f12409

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
@@ -70,6 +70,8 @@
7070
PermissionResultAllow,
7171
PermissionResultDeny,
7272
PermissionUpdate,
73+
PostCompactHookInput,
74+
PostCompactHookSpecificOutput,
7375
PostToolUseFailureHookInput,
7476
PostToolUseFailureHookSpecificOutput,
7577
PostToolUseHookInput,
@@ -539,6 +541,8 @@ async def call_tool(name: str, arguments: dict[str, Any]) -> Any:
539541
"StopHookInput",
540542
"SubagentStopHookInput",
541543
"PreCompactHookInput",
544+
"PostCompactHookInput",
545+
"PostCompactHookSpecificOutput",
542546
"NotificationHookInput",
543547
"SubagentStartHookInput",
544548
"PermissionRequestHookInput",

src/claude_agent_sdk/types.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ class PermissionResultDeny:
206206
| Literal["Stop"]
207207
| Literal["SubagentStop"]
208208
| Literal["PreCompact"]
209+
| Literal["PostCompact"]
209210
| Literal["Notification"]
210211
| Literal["SubagentStart"]
211212
| Literal["PermissionRequest"]
@@ -309,6 +310,14 @@ class PreCompactHookInput(BaseHookInput):
309310
custom_instructions: str | None
310311

311312

313+
class PostCompactHookInput(BaseHookInput):
314+
"""Input data for PostCompact hook events."""
315+
316+
hook_event_name: Literal["PostCompact"]
317+
trigger: Literal["manual", "auto"]
318+
compact_summary: str
319+
320+
312321
class NotificationHookInput(BaseHookInput):
313322
"""Input data for Notification hook events."""
314323

@@ -344,6 +353,7 @@ class PermissionRequestHookInput(BaseHookInput, _SubagentContextMixin):
344353
| StopHookInput
345354
| SubagentStopHookInput
346355
| PreCompactHookInput
356+
| PostCompactHookInput
347357
| NotificationHookInput
348358
| SubagentStartHookInput
349359
| PermissionRequestHookInput
@@ -390,6 +400,13 @@ class SessionStartHookSpecificOutput(TypedDict):
390400
additionalContext: NotRequired[str]
391401

392402

403+
class PostCompactHookSpecificOutput(TypedDict):
404+
"""Hook-specific output for PostCompact events."""
405+
406+
hookEventName: Literal["PostCompact"]
407+
additionalContext: NotRequired[str]
408+
409+
393410
class NotificationHookSpecificOutput(TypedDict):
394411
"""Hook-specific output for Notification events."""
395412

@@ -417,6 +434,7 @@ class PermissionRequestHookSpecificOutput(TypedDict):
417434
| PostToolUseFailureHookSpecificOutput
418435
| UserPromptSubmitHookSpecificOutput
419436
| SessionStartHookSpecificOutput
437+
| PostCompactHookSpecificOutput
420438
| NotificationHookSpecificOutput
421439
| SubagentStartHookSpecificOutput
422440
| PermissionRequestHookSpecificOutput

tests/test_tool_callbacks.py

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

824+
@pytest.mark.asyncio
825+
async def test_post_compact_hook_callback(self):
826+
"""Test that a PostCompact hook callback receives correct input and returns output."""
827+
hook_calls: list[dict[str, Any]] = []
828+
829+
async def post_compact_hook(
830+
input_data: HookInput, tool_use_id: str | None, context: HookContext
831+
) -> HookJSONOutput:
832+
hook_calls.append({"input": input_data, "tool_use_id": tool_use_id})
833+
return {
834+
"hookSpecificOutput": {
835+
"hookEventName": "PostCompact",
836+
"additionalContext": "Compaction logged successfully",
837+
}
838+
}
839+
840+
transport = MockTransport()
841+
query = Query(
842+
transport=transport, is_streaming_mode=True, can_use_tool=None, hooks={}
843+
)
844+
845+
callback_id = "test_post_compact_hook"
846+
query.hook_callbacks[callback_id] = post_compact_hook
847+
848+
request = {
849+
"type": "control_request",
850+
"request_id": "test-post-compact",
851+
"request": {
852+
"subtype": "hook_callback",
853+
"callback_id": callback_id,
854+
"input": {
855+
"session_id": "sess-1",
856+
"transcript_path": "/tmp/t",
857+
"cwd": "/home",
858+
"hook_event_name": "PostCompact",
859+
"trigger": "auto",
860+
"compact_summary": "Conversation summary...",
861+
},
862+
"tool_use_id": None,
863+
},
864+
}
865+
866+
await query._handle_control_request(request)
867+
868+
assert len(hook_calls) == 1
869+
assert hook_calls[0]["input"]["hook_event_name"] == "PostCompact"
870+
assert hook_calls[0]["input"]["trigger"] == "auto"
871+
assert hook_calls[0]["input"]["compact_summary"] == "Conversation summary..."
872+
873+
response_data = json.loads(transport.written_messages[-1])
874+
result = response_data["response"]["response"]
875+
assert result["hookSpecificOutput"]["hookEventName"] == "PostCompact"
876+
assert (
877+
result["hookSpecificOutput"]["additionalContext"]
878+
== "Compaction logged successfully"
879+
)
880+
824881

825882
class TestHookInitializeRegistration:
826883
"""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,
@@ -299,6 +301,34 @@ def test_permission_request_hook_input_with_suggestions(self):
299301
}
300302
assert len(hook_input["permission_suggestions"]) == 1
301303

304+
def test_post_compact_hook_input(self):
305+
"""Test PostCompactHookInput construction."""
306+
hook_input: PostCompactHookInput = {
307+
"session_id": "sess-1",
308+
"transcript_path": "/tmp/transcript.jsonl",
309+
"cwd": "/home/user/project",
310+
"hook_event_name": "PostCompact",
311+
"trigger": "auto",
312+
"compact_summary": "Summary of the compacted conversation...",
313+
}
314+
assert hook_input["hook_event_name"] == "PostCompact"
315+
assert hook_input["trigger"] == "auto"
316+
assert (
317+
hook_input["compact_summary"] == "Summary of the compacted conversation..."
318+
)
319+
320+
def test_post_compact_hook_input_manual_trigger(self):
321+
"""Test PostCompactHookInput with manual trigger."""
322+
hook_input: PostCompactHookInput = {
323+
"session_id": "sess-2",
324+
"transcript_path": "/tmp/transcript.jsonl",
325+
"cwd": "/home/user/project",
326+
"hook_event_name": "PostCompact",
327+
"trigger": "manual",
328+
"compact_summary": "User-initiated compaction summary",
329+
}
330+
assert hook_input["trigger"] == "manual"
331+
302332

303333
class TestHookSpecificOutputTypes:
304334
"""Test hook-specific output type definitions."""
@@ -345,6 +375,15 @@ def test_post_tool_use_output_has_updated_mcp_tool_output(self):
345375
}
346376
assert output["updatedMCPToolOutput"] == {"result": "modified"}
347377

378+
def test_post_compact_hook_specific_output(self):
379+
"""Test PostCompactHookSpecificOutput construction."""
380+
output: PostCompactHookSpecificOutput = {
381+
"hookEventName": "PostCompact",
382+
"additionalContext": "Compaction complete, summary logged",
383+
}
384+
assert output["hookEventName"] == "PostCompact"
385+
assert output["additionalContext"] == "Compaction complete, summary logged"
386+
348387

349388
class TestMcpServerStatusTypes:
350389
"""Test MCP server status type definitions."""

0 commit comments

Comments
 (0)