From 01f031919297b4f1d6cb1f883cc0dbc1481b73a3 Mon Sep 17 00:00:00 2001 From: Derek Barrera Date: Mon, 6 Apr 2026 09:50:36 -0400 Subject: [PATCH] fix(lsp): MEMORY LEAK: ensure typescript server uses native project config (#19953) --- packages/opencode/src/lsp/server.ts | 12 +++- packages/opencode/test/lsp/index.test.ts | 78 +++++++++++++++++++++ packages/web/src/content/docs/ecosystem.mdx | 2 + 3 files changed, 91 insertions(+), 1 deletion(-) diff --git a/packages/opencode/src/lsp/server.ts b/packages/opencode/src/lsp/server.ts index aa9bc884a8..7421ed5436 100644 --- a/packages/opencode/src/lsp/server.ts +++ b/packages/opencode/src/lsp/server.ts @@ -105,7 +105,17 @@ export namespace LSPServer { if (!tsserver) return const bin = await Npm.which("typescript-language-server") if (!bin) return - const proc = spawn(bin, ["--stdio"], { + + const args = ["--stdio", "--tsserver-log-verbosity", "off", "--tsserver-path", tsserver] + + if ( + !(await pathExists(path.join(root, "tsconfig.json"))) && + !(await pathExists(path.join(root, "jsconfig.json"))) + ) { + args.push("--ignore-node-modules") + } + + const proc = spawn(bin, args, { cwd: root, env: { ...process.env, diff --git a/packages/opencode/test/lsp/index.test.ts b/packages/opencode/test/lsp/index.test.ts index 7e514e39b1..ceadfe6518 100644 --- a/packages/opencode/test/lsp/index.test.ts +++ b/packages/opencode/test/lsp/index.test.ts @@ -1,6 +1,8 @@ import { describe, expect, spyOn, test } from "bun:test" import path from "path" +import fs from "fs/promises" import * as Lsp from "../../src/lsp/index" +import * as launch from "../../src/lsp/launch" import { LSPServer } from "../../src/lsp/server" import { Instance } from "../../src/project/instance" import { tmpdir } from "../fixture/fixture" @@ -52,4 +54,80 @@ describe("lsp.spawn", () => { await Instance.disposeAll() } }) + + test("spawns builtin Typescript LSP with correct arguments", async () => { + await using tmp = await tmpdir() + + // Create dummy tsserver to satisfy Module.resolve + const tsdk = path.join(tmp.path, "node_modules", "typescript", "lib") + await fs.mkdir(tsdk, { recursive: true }) + await fs.writeFile(path.join(tsdk, "tsserver.js"), "") + + const spawnSpy = spyOn(launch, "spawn").mockImplementation( + () => + ({ + stdin: {}, + stdout: {}, + stderr: {}, + on: () => {}, + kill: () => {}, + }) as any, + ) + + try { + await Instance.provide({ + directory: tmp.path, + fn: async () => { + await LSPServer.Typescript.spawn(tmp.path) + }, + }) + + expect(spawnSpy).toHaveBeenCalled() + const args = spawnSpy.mock.calls[0][1] as string[] + + expect(args).toContain("--tsserver-path") + expect(args).toContain("--tsserver-log-verbosity") + expect(args).toContain("off") + } finally { + spawnSpy.mockRestore() + } + }) + + test("spawns builtin Typescript LSP with --ignore-node-modules if no config is found", async () => { + await using tmp = await tmpdir() + + // Create dummy tsserver to satisfy Module.resolve + const tsdk = path.join(tmp.path, "node_modules", "typescript", "lib") + await fs.mkdir(tsdk, { recursive: true }) + await fs.writeFile(path.join(tsdk, "tsserver.js"), "") + + // NO tsconfig.json or jsconfig.json created here + + const spawnSpy = spyOn(launch, "spawn").mockImplementation( + () => + ({ + stdin: {}, + stdout: {}, + stderr: {}, + on: () => {}, + kill: () => {}, + }) as any, + ) + + try { + await Instance.provide({ + directory: tmp.path, + fn: async () => { + await LSPServer.Typescript.spawn(tmp.path) + }, + }) + + expect(spawnSpy).toHaveBeenCalled() + const args = spawnSpy.mock.calls[0][1] as string[] + + expect(args).toContain("--ignore-node-modules") + } finally { + spawnSpy.mockRestore() + } + }) }) diff --git a/packages/web/src/content/docs/ecosystem.mdx b/packages/web/src/content/docs/ecosystem.mdx index 30b53eeca7..055daf1419 100644 --- a/packages/web/src/content/docs/ecosystem.mdx +++ b/packages/web/src/content/docs/ecosystem.mdx @@ -32,6 +32,7 @@ You can also check out [awesome-opencode](https://github.com/awesome-opencode/aw | [opencode-shell-strategy](https://github.com/JRedeker/opencode-shell-strategy) | Instructions for non-interactive shell commands - prevents hangs from TTY-dependent operations | | [opencode-wakatime](https://github.com/angristan/opencode-wakatime) | Track OpenCode usage with Wakatime | | [opencode-md-table-formatter](https://github.com/franlol/opencode-md-table-formatter/tree/main) | Clean up markdown tables produced by LLMs | +| [opencode-morph-fast-apply](https://github.com/JRedeker/opencode-morph-fast-apply) | 10x faster code editing with Morph Fast Apply API and lazy edit markers | | [opencode-morph-plugin](https://github.com/morphllm/opencode-morph-plugin) | Fast Apply editing, WarpGrep codebase search, and context compaction via Morph | | [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode) | Background agents, pre-built LSP/AST/MCP tools, curated agents, Claude Code compatible | | [opencode-notificator](https://github.com/panta82/opencode-notificator) | Desktop notifications and sound alerts for OpenCode sessions | @@ -42,6 +43,7 @@ You can also check out [awesome-opencode](https://github.com/awesome-opencode/aw | [@plannotator/opencode](https://github.com/backnotprop/plannotator/tree/main/apps/opencode-plugin) | Interactive plan review with visual annotation and private/offline sharing | | [@openspoon/subtask2](https://github.com/spoons-and-mirrors/subtask2) | Extend opencode /commands into a powerful orchestration system with granular flow control | | [opencode-scheduler](https://github.com/different-ai/opencode-scheduler) | Schedule recurring jobs using launchd (Mac) or systemd (Linux) with cron syntax | +| [opencode-conductor](https://github.com/derekbar90/opencode-conductor) | Protocol-Driven Workflow: Automation of the Context -> Spec -> Plan -> Implement lifecycle. | | [micode](https://github.com/vtemian/micode) | Structured Brainstorm → Plan → Implement workflow with session continuity | | [octto](https://github.com/vtemian/octto) | Interactive browser UI for AI brainstorming with multi-question forms | | [opencode-background-agents](https://github.com/kdcokenny/opencode-background-agents) | Claude Code-style background agents with async delegation and context persistence |