Skip to content

fix(mcp): cleanup cancelled servers during connect failure#3919

Merged
seratch merged 1 commit into
openai:mainfrom
hsusul:fix/mcp-manager-cancelled-connect-cleanup
Jul 23, 2026
Merged

fix(mcp): cleanup cancelled servers during connect failure#3919
seratch merged 1 commit into
openai:mainfrom
hsusul:fix/mcp-manager-cancelled-connect-cleanup

Conversation

@hsusul

@hsusul hsusul commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Summary

When MCPServerManager is configured with suppress_cancelled_error=False, a CancelledError raised during connect() was re-raised without recording the server as failed. Sequential connect_all() failure cleanup only walks connected_servers + failed_servers, so the cancelled server was skipped. Under the recommended async with MCPServerManager(...) pattern, __aexit__ never runs if __aenter__ raises, so there is no later chance to call cleanup_all() — leaving transports / subprocesses / sessions open.

This aligns CancelledError handling with the existing BaseException path: always _record_failure(...) first, then re-raise when suppression is disabled.

Root cause

In _attempt_connect:

except asyncio.CancelledError as exc:
    if not self.suppress_cancelled_error:
        raise  # never recorded → never cleaned by connect_all failure path
    self._record_failure(server, exc, phase="connect")

Neighboring BaseException handling already records before re-raising, and the lifecycle reference requires partial connection failures to close every context already entered.

Reproduction

async with MCPServerManager([ok_server, cancelled_server], suppress_cancelled_error=False):
    ...
# CancelledError propagates; cancelled_server.cleanup() was never called

Before this change: cancelled server stays resource_open=True, cleanup_calls=0.
After this change: connect_all() failure cleanup runs cleanup() on the cancelled server.

Solution

Record the cancelled server via _record_failure before optionally re-raising, so connect_all()'s sequential failure cleanup includes it (same contract as fatal BaseException connect failures).

Test plan

  • Strengthened test_manager_cleanup_runs_on_cancelled_error_during_connect to assert the cancelled server is recorded and cleaned by connect_all() itself
  • Added test_manager_async_with_cleans_cancelled_server_when_unsuppressed covering the async with leak path
  • Ran .agents/skills/code-change-verification/scripts/run.sh (format, lint, typecheck, full tests) — all passed
  • git diff --check clean

Issue number

N/A — found via code-path review; no existing issue/PR covering this exact gap (nearby MCP cleanup work targets stdio semaphore / exit-stack reconnect issues, not this manager cancellation recording path).

Checks

  • I've added new tests, if relevant
  • I've run .agents/skills/code-change-verification/scripts/run.sh
  • I've confirmed all verification steps pass
  • If using Codex, I've run /review before submitting this PR

Compatibility and risk

  • Default suppress_cancelled_error=True behavior is unchanged (still records and does not re-raise).
  • Only the unsuppressed path gains recording-before-raise, matching documented cleanup expectations.
  • No public API shape changes.

Validation

make format / make lint — pass
make typecheck — pass
make tests — pass
code-change-verification — all commands passed

@hsusul
hsusul force-pushed the fix/mcp-manager-cancelled-connect-cleanup branch from 82abf55 to 1a26008 Compare July 23, 2026 05:38
Record CancelledError failures before re-raising when suppression is
disabled so connect_all() includes the cancelled server in its failure
cleanup path, matching BaseException handling and avoiding leaks under
async with where __aexit__ never runs.
@hsusul
hsusul force-pushed the fix/mcp-manager-cancelled-connect-cleanup branch from 1a26008 to 5457733 Compare July 23, 2026 05:38
@seratch seratch added this to the 0.19.x milestone Jul 23, 2026
@seratch
seratch enabled auto-merge (squash) July 23, 2026 07:45
@seratch
seratch merged commit 34ab935 into openai:main Jul 23, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants