fix(lsp-core): fail fast when LSP start or initialize hangs or dies asynchronously - #7244
Open
AceRothstein71 wants to merge 1 commit into
Open
Conversation
…synchronously An LSP server that fails asynchronously after spawn (exitCode still null at the synchronous check) or hangs on initialize blocked callers for the full 60s INIT_TIMEOUT_MS because pending JSON-RPC requests are never rejected on process exit. Bound the cold start with a short, env-overridable deadline (OMO_LSP_START_TIMEOUT_MS, default 10s): an AbortSignal guard around the initialize round-trip aborts with LspProcessExitedError when the process exits and with LspStartTimeoutError when the deadline elapses. Verified: failing-first regression suite (late-exit + silent-server red at the old behavior, green in ~1.2s after), healthy-server non-regression, env parsing unit + wiring proof, bun test packages/lsp-core 105 pass, repo-wide tsgo typecheck green. Evidence: .omo/evidence/20260824-6486-lsp-init-timeout/ Fixes code-yeongyu#6486
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Bounds the LSP cold start (spawn + initialize round-trip) with a short, env-overridable deadline so a server that dies asynchronously after spawn or hangs on initialize fails in seconds instead of blocking for the full 60s init ceiling.
constants.ts: newSTART_TIMEOUT_MS = 10_000plusresolveStartTimeoutMs()readingOMO_LSP_START_TIMEOUT_MS(positive integers only; missing/invalid values fall back to the default).errors.ts: newLspStartTimeoutErrorwhose message namesOMO_LSP_START_TIMEOUT_MSfor legitimately slow starters.transport.ts: newstartTimeoutMstimeout option andcreateStartGuard(), an AbortSignal that aborts withLspProcessExitedErrorwhenproc.exitedresolves (async spawn death, stderr tail included) or withLspStartTimeoutErrorwhen the deadline elapses. The deadline timer is unref'd and disposed once initialize settles.connection.ts:initialize()passes the guard signal into the initialize request and disposes the guard infinally.Manager, reaper, spawn resolution, and the existing
INIT_TIMEOUT_MSbackstop are untouched.Why
LspClientTransport.start()only checked the synchronousexitCoderight after spawn. An async spawn failure has not fired yet at that point (exitCodestill null), sostart()resolved healthy and control reachedinitialize(). Pending JSON-RPC requests are never rejected on process/stream close, so the initialize request sat untilINIT_TIMEOUT_MS = 60_000. In production this stalled background workers until a 5-minute stale-cancel silently discarded completed work; disabling uninstalled servers dropped worker time from ~5min to ~14s, which is exactly the bound this PR makes automatic.Verified
Failing-first regression suite
packages/lsp-core/src/lsp/transport-start-fast-fail.test.ts(given/when/then):.omo/evidence/20260824-6486-lsp-init-timeout/red-failing-first.txt.LspProcessExitedError, silent server rejectsLspStartTimeoutErrorat a 200ms injected deadline, a responsive echo server (fixtures/initialize-echo-server.mjs) still initializes and stops cleanly under an armed guard, and env parsing is unit-pinned.OMO_LSP_START_TIMEOUT_MS=200alone bounds a silent server without any explicit option.bun test packages/lsp-core: 105 pass / 0 fail (includes real cold-start integration suites).bun run typecheck(tsgo root + script + all package projects): green..omo/evidence/20260824-6486-lsp-init-timeout/.Risk
A legitimately slow-starting server that needs more than 10s to answer initialize now fails fast instead of waiting 60s; operators can raise
OMO_LSP_START_TIMEOUT_MS(the error message says so). The 60sINIT_TIMEOUT_MSremains as the outer backstop for consumers that override the start deadline upward.Fixes #6486
Summary by cubic
Fail fast when an LSP process dies after spawn or hangs on initialize. Previously we waited the full 60s init ceiling; now we bound start+initialize with a 10s default deadline, overridable via
OMO_LSP_START_TIMEOUT_MS.New start deadline:
START_TIMEOUT_MS = 10_000withresolveStartTimeoutMs()readingOMO_LSP_START_TIMEOUT_MS(positive integers only; defaults otherwise).New error:
LspStartTimeoutErrornames the env var in the message.Transport: add
startTimeoutMsoption andcreateStartGuard()that aborts initialize via an AbortSignal on process exit or on the deadline; timer is unref’d and disposed once initialize settles.Connection:
initialize()passes the guard signal and disposes it infinally.Required action for slow servers (>10s): raise
OMO_LSP_START_TIMEOUT_MSor passstartTimeoutMsto thelsp-coreclient to avoid premature failure.Written for commit d6cea97. Summary will update on new commits.