Skip to content

fix: keep stdin open for can_use_tool control responses#715

Open
Ch1ldKing wants to merge 1 commit intoanthropics:mainfrom
Ch1ldKing:fix-can-use-tool-stream-closed
Open

fix: keep stdin open for can_use_tool control responses#715
Ch1ldKing wants to merge 1 commit intoanthropics:mainfrom
Ch1ldKing:fix-can-use-tool-stream-closed

Conversation

@Ch1ldKing
Copy link
Copy Markdown

Summary

query() currently closes stdin too early when can_use_tool is used with an async prompt stream.

The SDK correctly switches permission handling to the stdio control path when can_use_tool is configured, but Query.wait_for_result_and_end_input() only waits for bidirectional control traffic when SDK MCP servers or hooks are present. That means stdin can be closed before the CLI sends a can_use_tool control request, or before the SDK writes the corresponding control response back to the CLI.

The result is that a valid permission decision can be replaced by a generic Stream closed transport failure.

Reproduction Scenario

A downstream application uses query() with:

  • an async prompt stream
  • ClaudeAgentOptions(can_use_tool=...)
  • a callback that denies or approves tool use through the normal permission callback path

A typical example is a path sandbox implemented with can_use_tool, where Claude attempts a file tool such as Read, Glob, or Grep and the host application returns a normal permission decision.

Before this change, stdin can already be closed by the time the CLI tries to round-trip the permission request through the SDK. Instead of preserving the callback result, the session can surface a generic Stream closed failure.

Why This Matters

can_use_tool is used to enforce policy decisions such as path restrictions and tool gating. When the callback result is replaced by a transport error, downstream callers lose the real denial reason and Claude sees the wrong tool outcome. That makes permission failures much harder to debug and can send the agent down the wrong recovery path.

Root Cause

There is a mismatch between the control path setup and the stdin lifecycle:

  • InternalClient.process_query() enables stdio-based permission handling whenever can_use_tool is configured
  • Query.wait_for_result_and_end_input() only delays end_input() for sdk_mcp_servers and hooks
  • stream_input() therefore closes stdin too early for can_use_tool control traffic

Fix

This change treats can_use_tool as another bidirectional control-path dependency in Query.wait_for_result_and_end_input().

The patch:

  • keeps stdin open until the first result arrives when can_use_tool is configured
  • updates the nearby docstrings and debug logging to reflect that behavior
  • adds regression coverage in tests/test_query.py for async iterable prompts with can_use_tool, including a control-request round trip

@Ch1ldKing Ch1ldKing marked this pull request as ready for review March 23, 2026 12:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant