-
Notifications
You must be signed in to change notification settings - Fork 550
Add missing hook output fields to match TypeScript SDK #226
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,149 @@ | ||
| """End-to-end tests for hook callbacks with real Claude API calls.""" | ||
|
|
||
| import pytest | ||
|
|
||
| from claude_agent_sdk import ( | ||
| ClaudeAgentOptions, | ||
| ClaudeSDKClient, | ||
| HookContext, | ||
| HookJSONOutput, | ||
| HookMatcher, | ||
| ) | ||
|
|
||
|
|
||
| @pytest.mark.e2e | ||
| @pytest.mark.asyncio | ||
| async def test_hook_with_permission_decision_and_reason(): | ||
| """Test that hooks with permissionDecision and reason fields work end-to-end.""" | ||
| hook_invocations = [] | ||
|
|
||
| async def test_hook( | ||
| input_data: dict, tool_use_id: str | None, context: HookContext | ||
| ) -> HookJSONOutput: | ||
| """Hook that uses permissionDecision and reason fields.""" | ||
| tool_name = input_data.get("tool_name", "") | ||
| print(f"Hook called for tool: {tool_name}") | ||
| hook_invocations.append(tool_name) | ||
|
|
||
| # Block Bash commands for this test | ||
| if tool_name == "Bash": | ||
| return { | ||
| "reason": "Bash commands are blocked in this test for safety", | ||
| "systemMessage": "⚠️ Command blocked by hook", | ||
| "hookSpecificOutput": { | ||
| "hookEventName": "PreToolUse", | ||
| "permissionDecision": "deny", | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would love to better understand the difference between
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| "permissionDecisionReason": "Security policy: Bash blocked", | ||
| }, | ||
| } | ||
|
|
||
| return { | ||
| "reason": "Tool approved by security review", | ||
| "hookSpecificOutput": { | ||
| "hookEventName": "PreToolUse", | ||
| "permissionDecision": "allow", | ||
| "permissionDecisionReason": "Tool passed security checks", | ||
| }, | ||
| } | ||
|
|
||
| options = ClaudeAgentOptions( | ||
| allowed_tools=["Bash", "Write"], | ||
| hooks={ | ||
| "PreToolUse": [ | ||
| HookMatcher(matcher="Bash", hooks=[test_hook]), | ||
| ], | ||
| }, | ||
| ) | ||
|
|
||
| async with ClaudeSDKClient(options=options) as client: | ||
| await client.query("Run this bash command: echo 'hello'") | ||
|
|
||
| async for message in client.receive_response(): | ||
| print(f"Got message: {message}") | ||
|
|
||
| print(f"Hook invocations: {hook_invocations}") | ||
| # Verify hook was called | ||
| assert "Bash" in hook_invocations, f"Hook should have been invoked for Bash tool, got: {hook_invocations}" | ||
|
|
||
|
|
||
| @pytest.mark.e2e | ||
| @pytest.mark.asyncio | ||
| async def test_hook_with_continue_and_stop_reason(): | ||
| """Test that hooks with continue_=False and stopReason fields work end-to-end.""" | ||
| hook_invocations = [] | ||
|
|
||
| async def post_tool_hook( | ||
| input_data: dict, tool_use_id: str | None, context: HookContext | ||
| ) -> HookJSONOutput: | ||
| """PostToolUse hook that stops execution with stopReason.""" | ||
| tool_name = input_data.get("tool_name", "") | ||
| hook_invocations.append(tool_name) | ||
|
|
||
| # Actually test continue_=False and stopReason fields | ||
| return { | ||
| "continue_": False, | ||
| "stopReason": "Execution halted by test hook for validation", | ||
| "reason": "Testing continue and stopReason fields", | ||
| "systemMessage": "🛑 Test hook stopped execution", | ||
| } | ||
|
|
||
| options = ClaudeAgentOptions( | ||
| allowed_tools=["Bash"], | ||
| hooks={ | ||
| "PostToolUse": [ | ||
| HookMatcher(matcher="Bash", hooks=[post_tool_hook]), | ||
| ], | ||
| }, | ||
| ) | ||
|
|
||
| async with ClaudeSDKClient(options=options) as client: | ||
| await client.query("Run: echo 'test message'") | ||
|
|
||
| async for message in client.receive_response(): | ||
| print(f"Got message: {message}") | ||
|
|
||
| print(f"Hook invocations: {hook_invocations}") | ||
| # Verify hook was called | ||
| assert "Bash" in hook_invocations, f"PostToolUse hook should have been invoked, got: {hook_invocations}" | ||
|
|
||
|
|
||
| @pytest.mark.e2e | ||
| @pytest.mark.asyncio | ||
| async def test_hook_with_additional_context(): | ||
| """Test that hooks with hookSpecificOutput work end-to-end.""" | ||
| hook_invocations = [] | ||
|
|
||
| async def context_hook( | ||
| input_data: dict, tool_use_id: str | None, context: HookContext | ||
| ) -> HookJSONOutput: | ||
| """Hook that provides additional context.""" | ||
| hook_invocations.append("context_added") | ||
|
|
||
| return { | ||
| "systemMessage": "Additional context provided by hook", | ||
| "reason": "Hook providing monitoring feedback", | ||
| "suppressOutput": False, | ||
| "hookSpecificOutput": { | ||
| "hookEventName": "PostToolUse", | ||
| "additionalContext": "The command executed successfully with hook monitoring", | ||
| }, | ||
| } | ||
|
|
||
| options = ClaudeAgentOptions( | ||
| allowed_tools=["Bash"], | ||
| hooks={ | ||
| "PostToolUse": [ | ||
| HookMatcher(matcher="Bash", hooks=[context_hook]), | ||
| ], | ||
| }, | ||
| ) | ||
|
|
||
| async with ClaudeSDKClient(options=options) as client: | ||
| await client.query("Run: echo 'testing hooks'") | ||
|
|
||
| async for message in client.receive_response(): | ||
| print(f"Got message: {message}") | ||
|
|
||
| print(f"Hook invocations: {hook_invocations}") | ||
| # Verify hook was called | ||
| assert "context_added" in hook_invocations, "Hook with hookSpecificOutput should have been invoked" | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we actually running on Windows for
test-examples? Sinceruns-on: ubuntu-latestThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, good catch