Skip to content

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

Description

@Bearmancer

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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions