Repository navigation
TypeScript LSP doesn't spawn when project is in subdirectory (Module.resolve uses ctx.directory instead of resolved root) #35396
Description
Bug summary
The TypeScript LSP server (packages/opencode/src/lsp/server.ts) fails to spawn when the TypeScript project lives in a subdirectory of the worktree (e.g. frontend-spa/ instead of the worktree root). The root is correctly resolved to the subdirectory via NearestRoot, but Module.resolve("typescript/lib/tsserver.js", ctx.directory) uses ctx.directory (the worktree root) instead of the already-resolved root, so it never finds tsserver.js.
Environment
- opencode 1.17.10 (Homebrew, macOS arm64)
- Config:
"lsp": true(all built-in LSPs enabled) - Project layout: worktree root has no
package.json;frontend-spa/haspackage.jsonwithtypescript ^5.0.0andnode_modules/typescript/lib/tsserver.jsinstalled.
Steps to reproduce
- Create a project where the TypeScript app lives in a subdirectory:
my-worktree/ ├── app/ # Python backend (Pyright works) └── frontend-spa/ ├── package.json # has "typescript": "^5.0.0" in devDependencies ├── package-lock.json ├── tsconfig.json ├── node_modules/ │ └── typescript/lib/tsserver.js # EXISTS └── src/index.ts - Enable LSP:
"lsp": truein opencode.json. - Run
opencode debug lsp diagnostics <any .ts file in frontend-spa/src/>.
Expected behavior
The TypeScript LSP spawns and returns diagnostics (type errors, etc.).
Actual behavior
opencode debug lsp diagnostics returns {} (empty object, no diagnostics). No tsserver or typescript-language-server process is spawned. ps aux | grep tsserver shows nothing.
Meanwhile, Pyright (Python) works correctly in the same worktree because it uses which("pyright-langserver") (PATH lookup) instead of Module.resolve(..., ctx.directory).
Root cause
In packages/opencode/src/lsp/server.ts, the Typescript server definition:
export const Typescript: Info = {
id: "typescript",
root: NearestRoot(
["package-lock.json", "bun.lockb", "bun.lock", "pnpm-lock.yaml", "yarn.lock"],
["deno.json", "deno.jsonc"],
),
extensions: [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts"],
async spawn(root, ctx) {
const tsserver = Module.resolve("typescript/lib/tsserver.js", ctx.directory) // ← BUG: uses ctx.directory
if (!tsserver) return
const bin = await Npm.which("typescript-language-server")
if (!bin) return
// ...
},
}The root parameter is correctly resolved by NearestRoot to frontend-spa/ (because that is where package-lock.json lives). However, Module.resolve is called with ctx.directory (the worktree root) instead of root. Since there is no node_modules/typescript/lib/tsserver.js at the worktree root, Module.resolve returns undefined and the LSP silently bails out.
Suggested fix
Use root (the already-resolved project root) instead of ctx.directory in the Module.resolve call:
async spawn(root, ctx) {
const tsserver = Module.resolve("typescript/lib/tsserver.js", root) // ← use root, not ctx.directory
if (!tsserver) return
// ...
}The same pattern appears in the Astro server definition (which also calls Module.resolve("typescript/lib/tsserver.js", ctx.directory)), so it likely has the same bug.
Workaround
Create a symlink at the worktree root pointing to the subdirectory's node_modules:
ln -s frontend-spa/node_modules ./node_modulesAfter this, Module.resolve("typescript/lib/tsserver.js", ctx.directory) finds tsserver.js via the symlink and the TypeScript LSP spawns correctly. Verified with opencode debug lsp diagnostics:
=== TYPESCRIPT ===
{
"/.../frontend-spa/src/__lsp_probe.ts": [
{
"message": "Type 'string' is not assignable to type 'number'.",
"severity": 1,
"code": 2322,
"source": "typescript"
},
...
]
}
Logs
opencode debug config shows "lsp": true and typescript is listed in enabled LSP servers. But no tsserver process is ever spawned:
$ ps aux | grep tsserver
(no output)
$ ps aux | grep pyright-langserver
iguinea 10947 ... node /opt/homebrew/bin/pyright-langserver --stdio
Impact
Any monorepo or subdirectory-layout project where the TypeScript app is not at the worktree root will have a silently broken TypeScript LSP. This is a common layout (backend + frontend split, Python + React, etc.).
Activity
github-actions commentedon Jul 5, 2026
This issue might be a duplicate of an existing issue. Please check:
github-actions commentedon Sep 4, 2026
To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.