ToolsNode.Stream leaks successful sibling streams when another tool fails
- 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
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