Skip to content

fix: catch ClosedResourceError in _handle_message error recovery path#2174

Closed
giulio-leone wants to merge 2 commits intomodelcontextprotocol:mainfrom
giulio-leone:fix/handle-message-closed-resource-error
Closed

fix: catch ClosedResourceError in _handle_message error recovery path#2174
giulio-leone wants to merge 2 commits intomodelcontextprotocol:mainfrom
giulio-leone:fix/handle-message-closed-resource-error

Conversation

@giulio-leone
Copy link

Problem

When a client disconnects during a stateless streamable-HTTP request, _handle_message catches the ClientDisconnect exception but then tries to send_log_message() back to the client. Since the session was already terminated and the write stream closed, this raises ClosedResourceError, which is unhandled in the TaskGroup and crashes the stateless session with an ExceptionGroup.

This is a different code path from what PR #1384 fixed. That PR addressed ClosedResourceError in the message router loop. This bug is in the error recovery path:

catch exception → try to log it to client → write stream already closed → ClosedResourceError → crash

Root Cause

In lowlevel/server.py, _handle_message has a catch-all exception handler (line ~418) that calls session.send_log_message() to notify the client about the error. When the error is a client disconnect, the write stream is already closed, so send_log_messagesend_notification_write_stream.send() raises ClosedResourceError.

Fix

Wrap the send_log_message() call with a try/except that catches anyio.ClosedResourceError and anyio.BrokenResourceError, since failing to notify a disconnected client is expected and harmless.

Tests

Added 3 tests to test_lowlevel_exception_handling.py:

  • test_exception_handling_survives_closed_write_streamClosedResourceError from send_log_message is caught
  • test_exception_handling_survives_broken_write_streamBrokenResourceError is caught
  • test_exception_handling_raises_when_configured_despite_closed_stream — with raise_exceptions=True, the original error is still re-raised

Validation

  • 636/636 server tests pass
  • 2 consecutive clean runs with 0 regressions

Fixes #2064

When a client disconnects during a request, _handle_message receives the
exception and tries to send_log_message() back to notify the client. Since
the write stream is already closed, this raises ClosedResourceError (or
BrokenResourceError), which is unhandled in the TaskGroup and crashes the
stateless session with an ExceptionGroup.

Wrap the send_log_message() call in _handle_message's Exception branch
with a try/except for ClosedResourceError and BrokenResourceError, since
failing to notify a disconnected client is expected and harmless.

Fixes modelcontextprotocol#2064
@giulio-leone giulio-leone force-pushed the fix/handle-message-closed-resource-error branch from fc4b36d to 6e63d96 Compare February 28, 2026 14:41
@giulio-leone
Copy link
Author

Closing in favor of #2177 which handles the same error path.

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.

ClientDisconnect during _handle_post_request crashes stateless session with ClosedResourceError

1 participant