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

  1. Increase the readiness timeout (context deadline) passed into WaitForServerReady.
  2. Enable debug logging (c.debug) to observe polling and see if the server is progressing or dead.
  3. Check the server's own logs to find why it never becomes ready.
  4. Ensure the server process isn't crashing right after initialize (it would never report running).
  5. 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

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

Related errors


AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29). Data as JSON: /api/errors/ac158baab3a8dcd1. Report an issue: GitHub.