[BUG] Python Agent SDK: `can_use_tool` requires streaming mode and dummy PreToolUse hook workaround

Resolved 💬 2 comments Opened Feb 10, 2026 by coygeek Closed Mar 11, 2026

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_tool requires streaming mode and a PreToolUse hook 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

  1. Create a Python script using the Agent SDK with can_use_tool but 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())
  1. Run the script
  2. Observe that can_use_tool is never called -- the stream closes before the callback fires
  1. 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])]},
    ),
):
  1. Run again -- can_use_tool now 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:

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.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗