Skip to content

fix(mcp): clear stale thread interrupt before MCP discovery - #10287

Closed
AJV20 wants to merge 1 commit into
NousResearch:mainfrom
AJV20:fix/mcp-stale-interrupt-cancellation
Closed

fix(mcp): clear stale thread interrupt before MCP discovery#10287
AJV20 wants to merge 1 commit into
NousResearch:mainfrom
AJV20:fix/mcp-stale-interrupt-cancellation

Conversation

@AJV20

@AJV20 AJV20 commented Apr 15, 2026

Copy link
Copy Markdown

Problem

Closes #9930

When an agent session is interrupted (Ctrl+C, gateway timeout, or /reset), the current thread's interrupt flag is set in _interrupted_threads. asyncio executor threads are pooled and reused across sessions, so a thread that carried an interrupt flag from a prior session will immediately cancel any new asyncio work dispatched to it — including MCP server discovery.

This causes all MCP servers to fail with asyncio.exceptions.CancelledError on the next session after any interruption, even though the new session has nothing to do with the prior interrupt.

Root Cause

register_mcp_servers() calls _run_on_mcp_loop(_discover_all(), timeout=120), which dispatches work to an executor thread. If that thread happens to be one that previously had its interrupt flag set and never got cleared (because the thread was returned to the pool rather than destroyed), is_interrupted() returns True at the start of discovery and CancelledError is raised immediately.

Fix

In register_mcp_servers(), temporarily clear the interrupt flag on the current thread before running _discover_all(), then restore it in a finally block so the original interrupt state is preserved:

from tools.interrupt import is_interrupted as _is_interrupted, set_interrupt as _set_interrupt
_was_interrupted = _is_interrupted()
if _was_interrupted:
    _set_interrupt(False)
try:
    _run_on_mcp_loop(_discover_all(), timeout=120)
finally:
    if _was_interrupted:
        _set_interrupt(True)

Testing

Verified by simulating stale interrupt state before calling register_mcp_servers(): 154 tools from 20 servers discovered successfully in ~3.8s with no CancelledError.

Fixes NousResearch#9930

When an agent session is interrupted (Ctrl+C or gateway timeout), the
current thread's interrupt flag is set in _interrupted_threads. asyncio
executor threads are pooled and reused across sessions, so a thread that
carried an interrupt flag from a prior session will immediately cancel
any new asyncio work dispatched to it — including MCP server discovery.

Fix: in register_mcp_servers(), temporarily clear the interrupt flag on
the current thread before running _discover_all(), then restore it
afterward in a finally block so the original interrupt state is not lost.
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists tool/mcp MCP client and OAuth labels Apr 26, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Likely duplicate of #14173 — same root cause: stale per-thread interrupt flag in _interrupted_threads cancels MCP registration. #14173 adds respect_interrupt=False kwarg; this PR clears/restores the flag manually. Different approach, same bug.

@teknium1

teknium1 commented May 7, 2026

Copy link
Copy Markdown
Contributor

Merged via #21276 (commit 46d1fc1 on main) — your commit was cherry-picked onto current main with authorship preserved via rebase-merge. E2E-verified live: simulated a stale interrupt on the current thread, ran register_mcp_servers, and discovery proceeded with a clear flag while the original interrupt state was restored on exit. Negative control confirmed the bug was real (_run_on_mcp_loop raises InterruptedError without the guard).

Note: your PR header said "Fixes #9930" but #9930 is actually a different bug (Python 3.11+ asyncio.CancelledError escaping except Exception in MCPServerTask.run, also still live on main). We left #9930 open for a follow-up. Thanks for the clean fix!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Medium — degraded but workaround exists tool/mcp MCP client and OAuth type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MCP reconnect fails with asyncio.CancelledError on Python 3.11+

3 participants