Skip to content

feat(lsp): non-blocking initialization + configurable diagnostics timeout#13332

Open
ojh102 wants to merge 3 commits intoanomalyco:devfrom
ojh102:feat/non-blocking-lsp-init
Open

feat(lsp): non-blocking initialization + configurable diagnostics timeout#13332
ojh102 wants to merge 3 commits intoanomalyco:devfrom
ojh102:feat/non-blocking-lsp-init

Conversation

@ojh102
Copy link

@ojh102 ojh102 commented Feb 12, 2026

What does this PR do?

On large projects (Android monorepo with kotlin-lsp), getClients() blocks 45+ seconds waiting for LSP init, which causes timeouts and makes the editor unusable during startup. The 3s diagnostics timeout is also too short for heavy language servers like ESLint.

This PR does two things:

  1. Makes getClients() non-blocking — changes the return type from Promise<LSPClient.Info[]> to LSPClient.Info[]. Returns immediately with whatever clients are ready (or [] during init), spawns servers in the background, and notifies via Bus.publish(Event.Updated) when new clients come online. The editor stays responsive from the first keystroke.

  2. Makes diagnostics timeout configurable — adds OPENCODE_EXPERIMENTAL_LSP_DIAGNOSTICS_TIMEOUT_MS flag (default bumped from 3s to 10s). Uses the existing number() helper and follows the same pattern as OPENCODE_EXPERIMENTAL_BASH_DEFAULT_TIMEOUT_MS.

Fixes #13328
Related: #7477, #13272, #13154

Files changed:

  • packages/opencode/src/lsp/index.ts — non-blocking getClients()
  • packages/opencode/src/lsp/index.test.ts — 6 tests for non-blocking behavior
  • packages/opencode/src/flag/flag.ts — new OPENCODE_EXPERIMENTAL_LSP_DIAGNOSTICS_TIMEOUT_MS flag
  • packages/opencode/src/lsp/client.ts — uses configurable timeout instead of hardcoded 3s
  • packages/opencode/src/lsp/client.test.ts — 3 tests for timeout configuration

How did you verify your code works?

  • 9 new tests (6 for non-blocking init, 3 for configurable timeout)
  • Full suite: 935 pass, 5 skip, 0 fail
  • Typecheck: 12/12 packages pass
  • Manually tested with kotlin-lsp on an Android monorepo — editor loads instantly, LSP features appear as servers become ready

…imeouts

LSP initialization blocks for 45+ seconds on large projects (e.g., kotlin-lsp
with Android monorepos), causing timeout errors before any language features
become available.

Make getClients() synchronous by returning immediately with [] when state is
not yet cached, then spawning LSP servers in the background via fire-and-forget
promises. Once a server is ready, Bus.publish(Event.Updated) triggers a UI
refresh so the editor picks up the new client.

Key changes:
- Add cachedState module-level variable populated by init()
- Convert getClients() from async to sync with early return pattern
- Move server.root() resolution and schedule() into fire-and-forget promises
- Bus.publish fires on successful client creation, not synchronously
- Add 6 tests covering non-blocking behavior, event firing, broken set,
  and deduplication via spawning map

Closes anomalyco#7477
- Add OPENCODE_EXPERIMENTAL_LSP_DIAGNOSTICS_TIMEOUT_MS flag with dynamic getter
- Integrate timeout into getDiagnostics() call (defaults to 10_000ms)
- Add comprehensive test suite (5 tests) covering default, custom, and invalid values
- All tests passing (937 pass, 5 skip, 0 fail)
- Typecheck clean (12 successful)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] LSP freezes and diagnostics timeout too short (3s) for heavy language servers

1 participant

Comments