anomalyco / anomalyco/opencode

[Feature] Document & expose global LSP activation (a custom server id bypasses NearestRoot gating)

Open
#48,785 0 comments 0 reactions 1 assignee View on GitHub

@kitlangton is already working on this.

Since Sep 13, 2026.

Dominant language
TypeScript
Stars
209k
Forks
27.5k
PR merge metrics
PR metrics pending

Description

opencode's built-in LSP servers are repo-root gated (NearestRoot([...markers...]), walk stops at ctx.directory), but a custom lsp entry whose id is not a built-in gets root = ctx.directory ΓÇö i.e. it activates globally, with no marker file required. This behaviour is undocumented and there is no explicit way to request it.

Mechanism (source, dev)

packages/opencode/src/lsp/lsp.ts ΓÇö config entries are merged into the server map:

if (cfg.lsp !== true) {
  for (const [name, item] of Object.entries(cfg.lsp)) {
    const existing = servers[name]
    if (item.disabled) { delete servers[name]; continue }
    servers[name] = {
      ...existing,
      id: name,
      root: existing?.root ?? (async (_file, ctx) => ctx.directory),   // <-- key line
      extensions: item.extensions ?? existing?.extensions ?? [],
      spawn: async (root) => ({ process: lspspawn(item.command[0], item.command.slice(1), { cwd: root, env: { ...process.env, ...item.env } }), initialization: item.initialization }),
    }
  }
}
  • New id ΓåÆ existing === undefined ΓåÆ root = ctx.directory (global; no markers).
  • Built-in id (e.g. biome, csharp) ΓåÆ inherits the built-in NearestRoot(...) ΓåÆ marker-gated.

The config schema (packages/core/src/config/lsp.ts) exposes only command, extensions, disabled, env, initialization ΓÇö there is no root or global field, and this behaviour is not in the docs (https://opencode.ai/docs/lsp/).

Verified globally (marker-free directory)

With ~/.config/opencode/opencode.jsonc entries using non-built-in ids:

"global-python": { "command": ["basedpyright-langserver","--stdio"], "extensions": [".py",".pyi"] },
"global-csharp": { "command": ["cmd","/c","roslyn-language-server","--stdio","--autoLoadProjects"], "extensions": [".cs",".csx"] },
"global-biome-code": { "command": ["cmd","/c","biome","lsp-proxy","--stdio"], "extensions": [".js",".jsx",".ts",".tsx",".mjs",".cjs",".mts",".cts"] },
"global-shell": { "command": ["cmd","/c","bash-language-server","start"], "extensions": [".sh",".bash",".zsh",".ksh"] },

In a bare temp dir with no lockfile/biome.json/.slnx anywhere on the walk path, writing broken files produced diagnostics (write-tool <diagnostics> block):

<diagnostics file="...\global-lsp-probe\probe2.py">
ERROR [1:6] "(" was not closed
ERROR [1:7] Expected parameter name
...
ERROR [1:37] Invalid expression term ';'      # .cs
ERROR [1:19] Expected an expression, or an assignment but instead found ';'.   # .ts
ERROR [1:11] Expected an expression, or an assignment but instead found ';'.   # .js

(The same directory yields nothing for .sh and .json ΓÇö separate issues, not this request.)

Request

  1. Document the rule: "a custom server id that does not match a built-in is launched with the workspace directory as its root, so it is not marker-gated."
  2. Optionally expose it explicitly, e.g. "root": "workspace" or "global": true on a server entry, so users don't have to discover a name-collision side effect.
  3. Consider the inverse too: a documented way to force marker-gating for a custom server.

Verified 2026-09-13 on opencode 1.18.30 (Windows 10 Pro 19045).


Environment (software)
item value
OS Microsoft Windows 10 Pro — 10.0.19045 (Build 19045), 64-bit
Shell PowerShell 7.6.6 (+ Windows Terminal)
opencode 1.18.30
plugins oh-my-openagent 4.19.4 · @cortexkit/opencode-antigravity-auth 2.2.1
runtimes Node v24.19.0 · npm 11.17.0 · Bun 1.4.2
LSP binaries biome 2.5.13 · basedpyright 1.40.1 · roslyn-language-server 5.12.0-1.26426.8 · bash-language-server 5.6.0 · shellcheck 0.11.0 · vscode-langservers-extracted 4.10.0 · PowerShellEditorServices 4.7.0 · PSScriptAnalyzer 1.25.0
logs ~/.local/share/opencode/log/opencode.log · ~/.omo/agent/OmO-debug.log · ~/.omo/agent/logs/config-reload.log

OS facts collected with:

Get-CimInstance Win32_OperatingSystem | Select-Object Caption,Version,BuildNumber,OSArchitecture

No hardware, hostname, username, or credentials included.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.