From a11008b7659cb612a61f45123ecac306ec647e85 Mon Sep 17 00:00:00 2001 From: Arthur Andrade Date: Sat, 10 Jan 2026 23:45:38 -0300 Subject: [PATCH] fix(lsp): cleanup orphaned LSP servers on session.deleted (#676) * fix(lsp): cleanup orphaned LSP servers on session.deleted When parallel background agent tasks complete, their LSP servers (for repos cloned to /tmp/) remain running until a 5-minute idle timeout. This causes memory accumulation with heavy parallel Sisyphus usage, potentially leading to OOM crashes. This change adds cleanupTempDirectoryClients() to LSPServerManager (matching the pattern used by SkillMcpManager.disconnectSession()) and calls it on session.deleted events. The cleanup targets idle LSP clients (refCount=0) for temporary directories (/tmp/, /var/folders/) where agent tasks clone repos. * chore: retrigger CI checks --- src/index.ts | 2 ++ src/tools/index.ts | 3 +++ src/tools/lsp/client.ts | 20 ++++++++++++++++++++ 3 files changed, 25 insertions(+) diff --git a/src/index.ts b/src/index.ts index 79c631af9..0db253849 100644 --- a/src/index.ts +++ b/src/index.ts @@ -63,6 +63,7 @@ import { createSisyphusTask, interactive_bash, startTmuxCheck, + lspManager, } from "./tools"; import { BackgroundManager } from "./features/background-agent"; import { SkillMcpManager } from "./features/skill-mcp-manager"; @@ -427,6 +428,7 @@ const OhMyOpenCodePlugin: Plugin = async (ctx) => { } if (sessionInfo?.id) { await skillMcpManager.disconnectSession(sessionInfo.id); + await lspManager.cleanupTempDirectoryClients(); } } diff --git a/src/tools/index.ts b/src/tools/index.ts index b02117b23..3ec21b0a3 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -10,8 +10,11 @@ import { lsp_rename, lsp_code_actions, lsp_code_action_resolve, + lspManager, } from "./lsp" +export { lspManager } + import { ast_grep_search, ast_grep_replace, diff --git a/src/tools/lsp/client.ts b/src/tools/lsp/client.ts index d72458990..725594bee 100644 --- a/src/tools/lsp/client.ts +++ b/src/tools/lsp/client.ts @@ -182,6 +182,26 @@ class LSPServerManager { this.cleanupInterval = null } } + + async cleanupTempDirectoryClients(): Promise { + const keysToRemove: string[] = [] + for (const [key, managed] of this.clients.entries()) { + const isTempDir = key.startsWith("/tmp/") || key.startsWith("/var/folders/") + const isIdle = managed.refCount === 0 + if (isTempDir && isIdle) { + keysToRemove.push(key) + } + } + for (const key of keysToRemove) { + const managed = this.clients.get(key) + if (managed) { + this.clients.delete(key) + try { + await managed.client.stop() + } catch {} + } + } + } } export const lspManager = LSPServerManager.getInstance()