Skip to content

Expose incremental tool output (stdout/stderr) for long-running tools while they execute #343

@remidebette

Description

@remidebette

Summary

There is currently no way for an SDK consumer to receive incremental stdout/stderr from a long-running tool (e.g. Bash) while it is still executing. Tool output is delivered exactly once, as a single complete block, when the command finishes. This makes it impossible to build a responsive UI for long-running commands — device-flow login URLs, build progress, "server ready on :3000" messages, etc. all stay hidden until the process exits.

This was surfaced downstream in the ACP adapter: agentclientprotocol/claude-agent-acp#739. The adapter maintainer confirmed the adapter has nothing to stream because the SDK doesn't expose this:

"If the SDK starts surfacing this information we can show it. but as far as I know, there isn't a way to get this information."

Filing here because the fix has to originate in the SDK.

What I verified (against @anthropic-ai/claude-agent-sdk@0.3.160)

I went through the published sdk.d.ts looking for any surface that exposes in-flight tool output or lets a consumer own tool execution. There isn't one:

  • CanUseTool / PermissionResult — the allow branch is { behavior: 'allow'; updatedInput?; updatedPermissions? }. It can gate or rewrite input, but has no field to return output and no way to delegate/take over execution. The CLI always runs the tool itself.
  • PreToolUse hook — same story: permissionDecision + updatedInput + additionalContext. No output, no delegation.
  • MessageDisplay hook — streams assistant message text line-deltas ("display-only"), never tool stdout.
  • includePartialMessages / SDKPartialAssistantMessage — partial assistant messages (text/thinking deltas) during generation. The tool_result still arrives as one complete block at completion.

So a consumer can see the model typing, but not the tool running.

Why the existing workaround isn't enough

The only current path is model-driven run_in_background + polling a BashOutput-style tool. That requires the model to choose to background the command, and the output arrives via polling rather than as a stream — it can't be relied on for transparent, real-time tool cards.

Proposed options

Either would unblock downstream clients (ACP/Zed and others):

  1. Emit incremental tool-output events. A streaming event (gated behind a flag like includePartialMessages, or a new includePartialToolOutput) carrying { tool_use_id, stdout/stderr delta } as a long-running tool produces output — mirroring how MessageDisplay flushes assistant-text line-deltas today.
  2. Let a consumer own execution. Extend the permission/tool model so CanUseTool (or a tool handler) can take over a tool call and stream its own output back — letting clients run the command in a client-owned terminal and stream natively.

Option 1 is the smaller, more general change and maps cleanly onto the adapter's existing terminal_output lifecycle.

Environment

  • @anthropic-ai/claude-agent-sdk@0.3.160
  • Downstream: @agentclientprotocol/claude-agent-acp@0.40.0, Zed client

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions