Skip to content

Handle asyncio.CancelledError in ToolNode#6764

Open
veeceey wants to merge 1 commit intolangchain-ai:mainfrom
veeceey:fix/issue-6726-handle-cancelled-error-in-tool-node
Open

Handle asyncio.CancelledError in ToolNode#6764
veeceey wants to merge 1 commit intolangchain-ai:mainfrom
veeceey:fix/issue-6726-handle-cancelled-error-in-tool-node

Conversation

@veeceey
Copy link

@veeceey veeceey commented Feb 8, 2026

Summary

Fixes #6726 - ToolNode now properly handles asyncio.CancelledError when handle_tool_errors=True.

Problem

When a tool execution is cancelled via asyncio.CancelledError, the ToolNode does not create an error ToolMessage even when handle_tool_errors=True. This leaves the message history in an invalid state where an AIMessage has tool_calls without corresponding ToolMessages, causing INVALID_CHAT_HISTORY errors on subsequent LLM calls.

Root cause: asyncio.CancelledError inherits from BaseException, not Exception, so it bypasses the existing exception handler which only catches Exception and its subclasses.

Solution

Added an explicit handler for asyncio.CancelledError in _execute_tool_async() that:

  1. Catches CancelledError before the general Exception handler
  2. When handle_tool_errors=True, converts it to an error ToolMessage with "Tool execution was cancelled" message
  3. When handle_tool_errors=False, re-raises the CancelledError (preserving existing behavior)

Test Plan

Added two comprehensive tests:

  • test_tool_node_handles_cancelled_error: Verifies that when a tool raises CancelledError and handle_tool_errors=True, it returns an error ToolMessage instead of propagating the error
  • test_tool_node_raises_cancelled_error_when_not_handled: Verifies that when handle_tool_errors=False, CancelledError is still raised
  • All existing tests pass (30 passed)

Changes

  • libs/prebuilt/langgraph/prebuilt/tool_node.py: Added CancelledError handler
  • libs/prebuilt/tests/test_tool_node.py: Added regression tests

Fixes langchain-ai#6726

When a tool execution raises asyncio.CancelledError and
handle_tool_errors=True, the ToolNode now creates an error
ToolMessage instead of letting the error propagate. This
prevents INVALID_CHAT_HISTORY errors when tools are cancelled.

Root cause: asyncio.CancelledError inherits from BaseException,
not Exception, so it wasn't caught by the existing exception
handler.

Changes:
- Add explicit asyncio.CancelledError handler in _execute_tool_async
  before the general Exception handler
- When handle_tool_errors=True, convert CancelledError to error
  ToolMessage with appropriate error content
- When handle_tool_errors=False, re-raise CancelledError
- Add comprehensive tests for both error handling scenarios
@veeceey
Copy link
Author

veeceey commented Feb 8, 2026

PR Review Summary

Status: ✅ READY TO MERGE

Well-handled edge case! This PR correctly catches which inherits from rather than , allowing proper error handling in ToolNode.

Key Strengths:

  • ✅ Minimal, focused change (14 lines added)
  • ✅ Proper exception hierarchy handling (BaseException vs Exception)
  • ✅ Follows existing error handling patterns
  • ✅ Tests both scenarios: handle_tool_errors=True and False
  • ✅ Includes docstring explaining the issue context

Technical Details:

Problem: inherits from , not , so it bypassed the existing exception handler and propagated unhandled

Solution:

  • Add explicit handler BEFORE the general handler
  • When : convert to error ToolMessage
  • When : re-raise to allow cancellation to propagate

Test Coverage:

  • Test 1: Verifies CancelledError converts to ToolMessage with handle_tool_errors=True
  • Test 2: Verifies CancelledError re-raises with handle_tool_errors=False
  • Both tests simulate realistic scenario (simulated tool cancellation)

This is a clean fix for a subtle Python exception hierarchy issue. Proper error handling for async tool cancellation. Ready for merge!

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.

handle_tool_errors=True does not catch asyncio.CancelledError, causing INVALID_CHAT_HISTORY

1 participant