fix(mcp): harden nested interruption detection

This commit is contained in:
Börje 2026-07-18 00:07:11 +02:00 committed by Teknium
parent 80209e51db
commit 1ae1dd8b2c
2 changed files with 45 additions and 6 deletions

View File

@ -121,6 +121,39 @@ def test_is_session_expired_rejects_mixed_group_with_user_interruption():
assert _is_session_expired_error(exc) is False
def test_exception_tree_finds_interruption_beyond_recursion_limit():
"""Arbitrarily deep wrapper trees must not overflow Python's call stack."""
import sys
from tools.mcp_tool import _exception_tree_contains_interruption
class NestedException(Exception):
exceptions: tuple[BaseException, ...]
exc = InterruptedError("cancel")
for _ in range(sys.getrecursionlimit() + 100):
wrapper = NestedException("wrapped")
wrapper.exceptions = (exc,)
exc = wrapper
assert _exception_tree_contains_interruption(exc) is True
def test_exception_tree_handles_cyclic_exceptions_graph():
"""Malformed exception graphs may contain cycles and must terminate safely."""
from tools.mcp_tool import _exception_tree_contains_interruption
class CyclicException(Exception):
exceptions: tuple[BaseException, ...]
first = CyclicException("first")
second = CyclicException("second")
first.exceptions = (second,)
second.exceptions = (first,)
assert _exception_tree_contains_interruption(first) is False
def test_is_session_expired_rejects_empty_message():
"""Bare exceptions with no message shouldn't match."""
from tools.mcp_tool import _is_session_expired_error

View File

@ -3902,12 +3902,18 @@ _SESSION_EXPIRED_MARKERS: tuple = (
def _exception_tree_contains_interruption(exc: BaseException) -> bool:
"""Return whether ``exc`` or any nested exception is user cancellation."""
if isinstance(exc, InterruptedError):
return True
return any(
_exception_tree_contains_interruption(child)
for child in getattr(exc, "exceptions", ())
)
stack = [exc]
seen: set[int] = set()
while stack:
current = stack.pop()
identity = id(current)
if identity in seen:
continue
seen.add(identity)
if isinstance(current, InterruptedError):
return True
stack.extend(getattr(current, "exceptions", ()))
return False
def _is_session_expired_error(exc: BaseException) -> bool: