charmbracelet/crush · error
timeout waiting for LSP server to be ready
Error message
timeout waiting for LSP server to be ready
What it means
WaitForServerReady polls the client (via ticker) until the LSP server reports running/ready, opening key config files meanwhile. If the context is cancelled or its deadline expires before the server becomes ready, the client is set to StateError and this fixed timeout message is returned. It means initialize may have completed but the server never signaled readiness.
Source
Thrown at internal/lsp/client.go:362
func (c *Client) WaitForServerReady(ctx context.Context) error {
// Set initial state
c.SetServerState(StateStarting)
// Try to ping the server with a simple request
ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop()
if c.debug {
slog.Debug("Waiting for LSP server to be ready...")
}
c.openKeyConfigFiles(ctx)
for {
select {
case <-ctx.Done():
c.SetServerState(StateError)
return fmt.Errorf("timeout waiting for LSP server to be ready")
case <-ticker.C:
// Check if client is running
if !c.client.IsRunning() {
if c.debug {
slog.Debug("LSP server not ready yet", "server", c.name)
}
continue
}
// Server is ready
c.SetServerState(StateReady)
if c.debug {
slog.Debug("LSP server is ready")
}
return nil
}
}
}View on GitHub (pinned to 7944b8e522)
Solutions
- Increase the readiness timeout (context deadline) passed into WaitForServerReady.
- Enable debug logging (c.debug) to observe polling and see if the server is progressing or dead.
- Check the server's own logs to find why it never becomes ready.
- Ensure the server process isn't crashing right after initialize (it would never report running).
- Retry the restart once deadlocks/hangs are ruled out.
Example fix
// before ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) ready, err := client.WaitForServerReady(ctx) // after ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute) ready, err := client.WaitForServerReady(ctx)
Defensive patterns
Strategy: retry
Validate before calling
// Poll cheaply before waiting on readiness
ticker := time.NewTicker(time.Second)
if !client.IsRunning() {
return fmt.Errorf("lsp process already exited; check server logs before waiting for readiness")
} Type guard
func isReadinessTimeout(err error) bool {
return err != nil && strings.Contains(err.Error(), "timeout waiting for LSP server to be ready")
} Try / catch
readyCtx, cancel := context.WithTimeout(ctx, 3*time.Minute)
defer cancel()
if err := client.WaitForServerReady(readyCtx); err != nil {
if isReadinessTimeout(err) {
slog.Warn("LSP not ready in time; inspect server logs", "error", err)
}
return err
} Prevention
- Size the readiness timeout to the largest expected project, not the smallest.
- Enable client debug logging to distinguish slow startup from a hung server.
- Check for server processes stuck on corrupted caches and clear them periodically.
When it happens
Trigger: Calling Restart or startServer with a context whose deadline expires before the server reaches a ready state — slow first-time server startup (indexing), server stuck after initialize, or server process died and IsRunning() never becomes true.
Common situations: Large projects where the server takes minutes to index before ready; gopls/python-language-server cold start on limited hardware; server hung due to a bad config file it tries to load; deadline set shorter than realistic startup time.
Understand the failure class
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
- failed to make request: %w
- failed to download from URL: %w
- failed to fetch URL: %w
- failed to fetch URL: %w
- failed to search for symbol: %w
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/ac158baab3a8dcd1.
Report an issue: GitHub.