charmbracelet/crush · error

failed to get LSPs: %w

Error message

failed to get LSPs: %w

What it means

GetLSPs issues GET /workspaces/{id}/lsps and wraps any transport failure from c.get with this message. The request did not produce an HTTP response at all; this is a connectivity problem, not an LSP-state problem. The real cause is wrapped via %w.

Source

Thrown at internal/client/proto.go:301

	if err != nil {
		return nil, fmt.Errorf("failed to get LSP diagnostics: %w", err)
	}
	defer rsp.Body.Close()
	if rsp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to get LSP diagnostics: status code %d", rsp.StatusCode)
	}
	var diagnostics map[protocol.DocumentURI][]protocol.Diagnostic
	if err := json.NewDecoder(rsp.Body).Decode(&diagnostics); err != nil {
		return nil, fmt.Errorf("failed to decode LSP diagnostics: %w", err)
	}
	return diagnostics, nil
}

// GetLSPs retrieves the LSP client states for a workspace.
func (c *Client) GetLSPs(ctx context.Context, id string) (map[string]proto.LSPClientInfo, error) {
	rsp, err := c.get(ctx, fmt.Sprintf("/workspaces/%s/lsps", id), nil, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to get LSPs: %w", err)
	}
	defer rsp.Body.Close()
	if rsp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to get LSPs: status code %d", rsp.StatusCode)
	}
	var lsps map[string]proto.LSPClientInfo
	if err := json.NewDecoder(rsp.Body).Decode(&lsps); err != nil {
		return nil, fmt.Errorf("failed to decode LSPs: %w", err)
	}
	return lsps, nil
}

// MCPGetStates retrieves the MCP client states for a workspace.
func (c *Client) MCPGetStates(ctx context.Context, id string) (map[string]proto.MCPClientInfo, error) {
	rsp, err := c.get(ctx, fmt.Sprintf("/workspaces/%s/mcp/states", id), nil, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to get MCP states: %w", err)
	}

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Inspect the wrapped error and fix the transport cause (start the server, correct the base URL).
  2. Confirm the crush server is running and listening on the expected address.
  3. Verify the workspace id path segment is a valid, live workspace.
  4. Add retry with backoff for transient network errors.

Example fix

// before
lsps, err := client.GetLSPs(ctx, wsID)
if err != nil { panic(err) }
// after
lsps, err := client.GetLSPs(ctx, wsID)
if err != nil {
    log.Printf("GetLSPs failed, server may be down: %v", err)
    return nil, fmt.Errorf("GetLSPs: %w", err)
}
Defensive patterns

Strategy: retry

Validate before calling

conn, err := net.DialTimeout("tcp", serverHostPort, 3*time.Second)
if err != nil { return fmt.Errorf("crush server not reachable: %w", err) }
conn.Close()

Type guard

func isRetryableNetworkErr(err error) bool {
	var ne net.Error
	if errors.As(err, &ne) { return true }
	return errors.Is(err, context.DeadlineExceeded) || errors.Is(err, syscall.ECONNREFUSED) || errors.Is(err, syscall.ECONNRESET)
}

Try / catch

var lsps map[string]proto.LSPClientInfo
err := retry.Do(3, backoff.Exponential(200*time.Millisecond), func() error {
	var e error
	lsps, e = client.GetLSPs(ctx, wsID)
	return e
})
if err != nil { return fmt.Errorf("GetLSPs after retries: %w", err) }

Prevention

When it happens

Trigger: Calling GetLSPs(ctx, id) (directly or via LSPGetStates) when the server is unreachable, the URL is wrong, the connection is refused/reset, or ctx is canceled mid-request.

Common situations: Daemon not running when a TUI/plugin asks for LSP states; wrong port in client config; machine sleep/resume killing the socket; expired context during long sessions.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


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