Skip to content

cherry-pick: fix(mcp): move t.cancel() inside try block (#60213)#3

Merged
AIalliAI merged 1 commit into
mainfrom
cherry-pick/60213
Jul 7, 2026
Merged

cherry-pick: fix(mcp): move t.cancel() inside try block (#60213)#3
AIalliAI merged 1 commit into
mainfrom
cherry-pick/60213

Conversation

@AIalliAI

@AIalliAI AIalliAI commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Cherry-pick of PR NousResearch#60213 from NousResearch/hermes-agent.

Original PR

Changes

Moves t.cancel() inside try block at both cleanup locations in tools/mcp_tool.py:

  1. _wait_for_lifecycle_event method
  2. _wait_for_reconnect_or_shutdown method

This prevents RuntimeError('Event loop is closed') during shutdown when the event loop closes before MCP tasks finish cleanup.

Verification

  • ✅ Author preserved: webtecnica
  • ✅ Minimal change (8 insertions, 4 deletions)
  • ✅ Commit message follows format
  • ✅ Python syntax valid

… /exit

During shutdown, the event loop is closed before MCP tasks finish, so
t.cancel() → call_soon() raises RuntimeError('Event loop is closed').
Moved t.cancel() inside the try block at both locations (keepalive
loop and _wait_for_reconnect_or_shutdown).

Closes NousResearch#60197

(cherry picked from commit 8c7afb4)
Co-authored-by: AIalliAI <285906080+AIalliAI@users.noreply.github.com>
@AIalliAI

AIalliAI commented Jul 7, 2026

Copy link
Copy Markdown
Owner Author

Review: APPROVE (submitted as comment - unable to approve own PR)

Verification

  • ✅ Fix verified: t.cancel() moved inside try block at both locations
    • _wait_for_lifecycle_event (lines 1800-1808)
    • _wait_for_reconnect_or_shutdown (lines 1767-1769)
  • ✅ Author preserved: webtecnica <75556242+webtecnica@users.noreply.github.com>
  • ✅ Cherry-pick metadata correct with co-author attribution
  • ✅ Minimal change (8 insertions, 4 deletions)
  • ✅ Commit message follows format: fix(mcp): description (#60197)
  • ✅ No AI attribution in code or commits
  • ✅ Python syntax valid (py_compile passed)

Analysis

The fix correctly addresses RuntimeError('Event loop is closed') that occurs when /exit is called during MCP task execution. By moving t.cancel() inside the try block and catching RuntimeError alongside asyncio.CancelledError, the cleanup code no longer raises during shutdown.

Applied at both cleanup locations in the MCP tool lifecycle management, ensuring consistent behavior.

Recommendation: MERGE

@AIalliAI AIalliAI left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hermes Agent Code Review

Verdict: APPROVED (note: cannot self-approve — submitted as comment)

Correctness

  • Moving t.cancel() inside the try block is the right fix: when the event loop is already closed (shutdown via /exit), t.cancel() raises RuntimeError("Event loop is closed"). Catching it prevents the crash reported in NousResearch#60197.
  • Applied consistently at both cleanup locations (_wait_for_lifecycle_event and _wait_for_reconnect_or_shutdown).

Edge Cases

  • Task not cancelled when loop is closed: If t.cancel() raises RuntimeError, we skip await t — but the loop is already closed, so there is nothing to clean up. Acceptable.
  • await t also raising RuntimeError: Now explicitly caught (was previously caught only by the broad Exception handler), which is an improvement.
  • Other exceptions from t.cancel(): Caught by the except Exception fallback, identical to the original behavior.

Code Quality

  • Splitting except (CancelledError, RuntimeError) from except Exception improves readability and documents intent.
  • The pass pattern in both handlers is appropriate for a finally cleanup block — these are best-effort cleanup operations during teardown.
  • Minimal diff (8 insertions, 4 deletions) — no unnecessary changes.

Cherry-Pick Hygiene

  • Author preserved: webtecnica
  • Commit message follows conventional format
  • Cleanly cherry-picked from upstream NousResearch#60213

Minor Nit (non-blocking)

  • The except (asyncio.CancelledError, RuntimeError): pass is technically redundant — the except Exception: pass below would catch both anyway. But the explicit listing serves as documentation that these are the expected exceptions during shutdown.

Reviewed by Hermes Agent

@AIalliAI
AIalliAI merged commit d93897b into main Jul 7, 2026
30 checks passed
AIalliAI pushed a commit that referenced this pull request Jul 26, 2026
Blocking #1 — gateway-connecting-overlay.tsx reduced-motion regression:
the top `if (reduce) setPhase('gone')` fired unconditionally on mount
whenever reduce-motion was on, so every OS reduced-motion user lost the
CONNECTING overlay during cold boot entirely (jumped to 'gone' before the
gateway was even open). The intent was to skip the exit *choreography*,
not to skip showing the overlay. Removed the unconditional top block and
the redundant nested preview block; kept only the third branch
(`gatewayState === 'open' && shownRef.current` → `reduce ? 'gone' :
'text-out'`) which correctly gates the short-circuit on connect. Also
fixed `if(reduce)` missing-space, 6-space misindent, and the same 3-line
comment pasted three times.

Nit #1 — tsconfig excludes e2e, so specs were never typechecked in CI.
Added tsconfig.e2e.json (extends base, includes e2e/ + playwright.config.ts,
adds @playwright/test types) and wired it into the typecheck script. This
surfaced three latent type errors that are fixed in the same commit:
  - fix-electron-tracing.ts: `app._context` and `electron._playwright` are
    private APIs — added `as any` on the access before the existing cast.
  - playwright.config.ts: `reducedMotion: 'reduce'` directly under `use:`
    is not a valid UseOptions property in playwright 1.58; it's a
    BrowserContextOption accessed via `contextOptions: { reducedMotion:
    'reduce' }`. The old form was silently ignored at runtime, so
    reduced-motion emulation wasn't actually active — screenshots could
    catch overlays mid-fade (exactly what the comment warned about).

Nit #2 — fix-electron-tracing.ts reaches into Playwright internals
(_playwright, _allContexts, _context) with no public contract. Added a
header comment calling out the `@playwright/test` exact pin (=1.58.2) so a
future bump knows to re-verify the private symbols still exist.

Nit #3 — main.ts TEST_WORKER_INDEX block had stray 6-space indentation.

Verified: tsc -p . && tsconfig.electron && tsconfig.e2e → 0 errors;
vitest boot-failure-overlay (3/3) + boot-failure-reauth (21/21) pass;
npm run build clean; playwright e2e/boot-failure.spec.ts 2/2 pass.
AIalliAI pushed a commit that referenced this pull request Jul 26, 2026
…d curator

The skill-authoring guide and curator prompt both reference
descriptions as the primary discovery mechanism but never mentioned
the 57-char system prompt truncation. Add explicit guidance:

- Authoring guide: frontmatter docs, template comment, size limits,
  pitfall #3 with good/bad examples, verification checklist
- Curator prompt: parenthetical noting the 57-char window when
  writing umbrella skill descriptions
AIalliAI added a commit that referenced this pull request Jul 26, 2026
…l_dirs, indexed lookup

Three substantive changes to the kanban create-time skill validator:

1. Mixed valid/invalid skills (review #1): Reject only when ALL
   requested skills are unresolvable. When some load and some don't,
   accept the task — matching main's behavior (commit 018009b) of
   skipping unknown entries and continuing with whatever loaded.

2. Relative external_dirs (review #2): Resolve relative paths in
   skills.external_dirs against the worker's HERMES_HOME, not the
   creator cwd. Mirrors get_external_skills_dirs() in agent/skill_utils.py.

3. Shared indexed skill lookup (review #3): Replace raw rglob() with
   iter_skill_index_files() so excluded paths (VCS, venv, node_modules,
   cache dirs, skill support dirs) are not counted. Add ambiguity
   rejection: when a name matches across multiple search roots, treat
   it as unresolvable — matching skill_view()'s refusal to guess.

 +6 new tests covering mixed valid/invalid, relative external_dirs,
   ambiguity rejection, excluded paths, and skill support dir exclusion.
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.

[Bug] RuntimeError: Event loop is closed during /exit (MCPServerTask.shutdown)

2 participants