cloudwego / cloudwego/eino

ToolsNode.Stream leaks successful sibling streams when another tool fails

Open
#1,256 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
Go
Stars
13k
Forks
1.1k
Avg merge
4h 6m
Merged PRs (30d)
41

Description

## Bug

When `ToolsNode.Stream` executes multiple tools and one tool returns a stream while another returns a regular error, the method returns the error without closing the successful sibling stream. A producer using an unbuffered `schema.Pipe` can therefore remain blocked after `ToolsNode.Stream` has returned.

Verified on `main` at `9d983b36a5112a1c233056b1a099825298fafb8f`.

## Reproduction

1. Create two `StreamableTool` implementations.
2. The first returns a reader backed by `schema.Pipe[string](0)` and starts a producer blocked in `Send`.
3. The second returns a sentinel error from `StreamableRun`.
4. Call `ToolsNode.Stream` with both tool calls.
5. The call returns the second tool error, but the first producer does not observe reader closure and remains blocked.

A deterministic regression test confirms that the producer still has not exited one second after the API returns.

## Root cause

`parallelRunToolCall` waits for both endpoints and stores the successful reader in the corresponding `toolCallTask`. During the subsequent error scan, `ToolsNode.Stream` returns immediately for a non-interrupt error. The successful readers are only transferred to `MergeStreamReaders` on the all-success path, so this early return abandons resources that the caller cannot access or close.

This violates the stream ownership contract: once `ToolsNode` accepts a returned reader, every return path must either transfer ownership to its caller or close it.

## Scope and intended fix

The issue is limited to `ToolsNode.Stream` early-error cleanup. I plan to add focused cleanup for both standard and enhanced successful streams, preserve the existing interrupt/rerun behavior, and add regression coverage for mixed success/error execution. I expect to send the focused fix within 24 hours.

Contributor guide

Open the contributing guide

Research direction

Start at the ToolsNode.Stream entry point and trace parallelRunToolCall, toolCallTask, and MergeStreamReaders, focusing on the early non-interrupt error path. Reproduce the mixed success/error case with an unbuffered schema.Pipe, then add regression coverage showing successful standard and enhanced streams are closed while existing interrupt and rerun behavior remains unchanged.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
backend
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
75/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.