[PR #171] [CLOSED] fix(deepagents): resolve dangling tool_call_id when rejecting parallel tool calls #176

Closed
opened 2026-02-16 06:17:21 -05:00 by yindo · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/langchain-ai/deepagentsjs/pull/171
Author: @christian-bromann
Created: 2/2/2026
Status: Closed

Base: mainHead: cb/dangling-tool-calls-hitl


📝 Commits (4)

📊 Changes

7 files changed (+473 additions, -196 deletions)

View changed files

📝 libs/deepagents/src/agent.ts (+8 -2)
📝 libs/deepagents/src/middleware/fs.int.test.ts (+7 -1)
📝 libs/deepagents/src/middleware/hitl.int.test.ts (+128 -41)
📝 libs/deepagents/src/middleware/index.test.ts (+114 -61)
📝 libs/deepagents/src/middleware/index.ts (+4 -1)
📝 libs/deepagents/src/middleware/patch_tool_calls.test.ts (+77 -50)
📝 libs/deepagents/src/middleware/patch_tool_calls.ts (+135 -40)

📄 Description

Summary

  • Fix middleware ordering to resolve dangling tool_call_id error when rejecting interrupted tool calls in parallel execution
  • Add regression test for the parallel tool call rejection scenario

Problem

When a single user request causes the agent to call two tools in parallel (interrupted_tool + free_tool), and interruptOn pauses on interrupted_tool, rejecting the interrupt via Command({ resume: { decisions: [{ type: "reject" }] } }) leaves a dangling tool_call_id. The provider then throws:

MiddlewareError: 400 An assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'. The following tool_call_ids did not have response messages: call_...

Root Cause

The middleware ordering in createDeepAgent was:

  1. createPatchToolCallsMiddleware() (ran early)
  2. ... other middleware ...
  3. humanInTheLoopMiddleware() (ran last)

When resuming with a reject decision:

  1. patchToolCalls.beforeAgent runs first and patches dangling tool calls
  2. humanInTheLoopMiddleware.beforeAgent then processes the reject decision, modifying messages in a way that conflicts with the earlier patching

Solution

Move createPatchToolCallsMiddleware() to run after humanInTheLoopMiddleware(), ensuring it sees the final message state after HITL has processed resume decisions and can properly patch any remaining dangling tool calls.

Fixes #150


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/langchain-ai/deepagentsjs/pull/171 **Author:** [@christian-bromann](https://github.com/christian-bromann) **Created:** 2/2/2026 **Status:** ❌ Closed **Base:** `main` ← **Head:** `cb/dangling-tool-calls-hitl` --- ### 📝 Commits (4) - [`90ea89b`](https://github.com/langchain-ai/deepagentsjs/commit/90ea89b96f5213c0ce94bf039b32a43c1aff28f1) fix: resolve dangling tool_call_id when rejecting parallel tool calls - [`4f4908c`](https://github.com/langchain-ai/deepagentsjs/commit/4f4908c0f327807d17043f4f3934f431efa6c805) improve tests - [`22be0e9`](https://github.com/langchain-ai/deepagentsjs/commit/22be0e9950a53e7924058d2d2c619cd718f724a4) cr - [`c2fe270`](https://github.com/langchain-ai/deepagentsjs/commit/c2fe270fa6fa504ced84148867816c6c3d6c3852) more test tweaks ### 📊 Changes **7 files changed** (+473 additions, -196 deletions) <details> <summary>View changed files</summary> 📝 `libs/deepagents/src/agent.ts` (+8 -2) 📝 `libs/deepagents/src/middleware/fs.int.test.ts` (+7 -1) 📝 `libs/deepagents/src/middleware/hitl.int.test.ts` (+128 -41) 📝 `libs/deepagents/src/middleware/index.test.ts` (+114 -61) 📝 `libs/deepagents/src/middleware/index.ts` (+4 -1) 📝 `libs/deepagents/src/middleware/patch_tool_calls.test.ts` (+77 -50) 📝 `libs/deepagents/src/middleware/patch_tool_calls.ts` (+135 -40) </details> ### 📄 Description ## Summary - Fix middleware ordering to resolve dangling `tool_call_id` error when rejecting interrupted tool calls in parallel execution - Add regression test for the parallel tool call rejection scenario ## Problem When a single user request causes the agent to call two tools in parallel (`interrupted_tool` + `free_tool`), and `interruptOn` pauses on `interrupted_tool`, rejecting the interrupt via `Command({ resume: { decisions: [{ type: "reject" }] } })` leaves a dangling `tool_call_id`. The provider then throws: ``` MiddlewareError: 400 An assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'. The following tool_call_ids did not have response messages: call_... ``` ## Root Cause The middleware ordering in `createDeepAgent` was: 1. `createPatchToolCallsMiddleware()` (ran early) 2. ... other middleware ... 3. `humanInTheLoopMiddleware()` (ran last) When resuming with a reject decision: 1. `patchToolCalls.beforeAgent` runs first and patches dangling tool calls 2. `humanInTheLoopMiddleware.beforeAgent` then processes the reject decision, modifying messages in a way that conflicts with the earlier patching ## Solution Move `createPatchToolCallsMiddleware()` to run **after** `humanInTheLoopMiddleware()`, ensuring it sees the final message state after HITL has processed resume decisions and can properly patch any remaining dangling tool calls. Fixes #150 --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
yindo added the pull-request label 2026-02-16 06:17:21 -05:00
yindo closed this issue 2026-02-16 06:17:21 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/deepagentsjs#176