From 0c127879c0b5b1628d9ce657b7c0e31832000da8 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 79c631af..0db25384 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 b02117b2..3ec21b0a 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 d7245899..725594be 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()