[BUG] Python Agent SDK: `can_use_tool` requires streaming mode and dummy PreToolUse hook workaround
Preflight Checklist
- [x] I have searched existing issues and this hasn't been reported yet
- [x] This is a single bug report (please file separate reports for different bugs)
- [x] I am using the latest version of Claude Code
What's Wrong?
The Python Agent SDK's can_use_tool callback does not work unless you also register a dummy PreToolUse hook that returns {"continue_": True}. Without this workaround, the stream closes before the permission callback can be invoked.
The official documentation explicitly acknowledges this as a workaround:
Note: In Python,can_use_toolrequires streaming mode and aPreToolUsehook that returns{"continue_": True}to keep the stream open. Without this hook, the stream closes before the permission callback can be invoked.
And in the code examples:
# Required workaround: dummy hook keeps the stream open for can_use_tool
async def dummy_hook(input_data, tool_use_id, context):
return {"continue_": True}
What Should Happen?
The can_use_tool callback should work without requiring a dummy hook or an explicit switch to streaming input mode. Passing can_use_tool in ClaudeAgentOptions with a simple string prompt should be sufficient for the SDK to keep the stream open and invoke the callback when permission is needed. The TypeScript SDK handles this transparently.
Error Messages/Logs
No explicit error - the stream simply closes before `can_use_tool` is invoked,
causing the agent to auto-deny tool use or hang.
Steps to Reproduce
- Create a Python script using the Agent SDK with
can_use_toolbut WITHOUT the dummy hook or streaming input:
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions
from claude_agent_sdk.types import PermissionResultAllow
async def can_use_tool(tool_name, input_data, context):
print(f"Permission requested for: {tool_name}")
return PermissionResultAllow(updated_input=input_data)
async def main():
async for message in query(
prompt="List files in the current directory using Bash",
options=ClaudeAgentOptions(can_use_tool=can_use_tool),
):
if hasattr(message, "result"):
print(message.result)
asyncio.run(main())
- Run the script
- Observe that
can_use_toolis never called -- the stream closes before the callback fires
- Now add both workarounds required by the docs -- streaming input mode AND the dummy hook:
from claude_agent_sdk.types import HookMatcher
async def dummy_hook(input_data, tool_use_id, context):
return {"continue_": True}
async def prompt_stream():
yield {
"type": "user",
"message": {"role": "user", "content": "List files in the current directory using Bash"},
}
# Update query call to use streaming input + dummy hook:
async for message in query(
prompt=prompt_stream(),
options=ClaudeAgentOptions(
can_use_tool=can_use_tool,
hooks={"PreToolUse": [HookMatcher(matcher=None, hooks=[dummy_hook])]},
),
):
- Run again --
can_use_toolnow works correctly
Environment
| Field | Value |
|-------|-------|
| Claude Code Version | 2.1.38 |
| Claude Model | Opus |
| Platform | Anthropic API |
| Operating System | macOS |
| Terminal/Shell | Terminal.app |
Is This a Regression?
- [ ] Yes, this worked in a previous version
- [ ] No, this never worked
- [x] I don't know
Additional Information
This is documented as a known issue in the official Agent SDK docs at:
- https://platform.claude.com/docs/en/agent-sdk/user-input (Note box after the first code example)
Both the tool approval example and the clarifying questions example include two workarounds: (1) streaming input mode via an async generator instead of a string prompt, and (2) a dummy PreToolUse hook. The TypeScript SDK does not appear to require either workaround -- it works with string prompts and no dummy hook.
This is a developer experience issue -- the need for streaming mode plus a dummy hook is non-obvious and makes the simplest can_use_tool example significantly more complex than necessary. The SDK should handle this internally when can_use_tool is provided.
This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗