charmbracelet/crush · error

failed to decode LSPs: %w

Error message

failed to decode LSPs: %w

What it means

GetLSPs received a 200 response but json.Decode failed to unmarshal the body into map[string]proto.LSPClientInfo. The payload did not match the expected LSP client state schema.

Source

Thrown at internal/client/proto.go:309

	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)
	}
	defer rsp.Body.Close()
	if rsp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to get MCP states: status code %d", rsp.StatusCode)
	}
	var states map[string]proto.MCPClientInfo
	if err := json.NewDecoder(rsp.Body).Decode(&states); err != nil {
		return nil, fmt.Errorf("failed to decode MCP states: %w", err)
	}

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Match client and server versions; rebuild the client against the current proto package.
  2. Capture the raw body and log it alongside the decode error to identify schema drift.
  3. Bypass or reconfigure proxies that may alter the response body.
  4. Restart the server if truncation is suspected.

Example fix

// before
var lsps map[string]proto.LSPClientInfo
if err := json.NewDecoder(rsp.Body).Decode(&lsps); err != nil { return nil, err }
// after
body, _ := io.ReadAll(rsp.Body)
var lsps map[string]proto.LSPClientInfo
if err := json.Unmarshal(body, &lsps); err != nil {
    return nil, fmt.Errorf("failed to decode LSPs: %w (body: %.200s)", err, body)
}
Defensive patterns

Strategy: type-guard

Validate before calling

body, _ := io.ReadAll(rsp.Body)
var probe map[string]json.RawMessage
if err := json.Unmarshal(body, &probe); err != nil { return fmt.Errorf("LSPs payload is not an object: %w", err) }

Type guard

func isLSPClientInfoMap(v any) (map[string]proto.LSPClientInfo, bool) {
	m, ok := v.(map[string]proto.LSPClientInfo)
	return m, ok && v != nil
}

Try / catch

var lsps map[string]proto.LSPClientInfo
if err := json.Unmarshal(body, &lsps); err != nil {
	return nil, fmt.Errorf("unexpected LSPs payload (server/client version mismatch?): %w", err)
}

Prevention

When it happens

Trigger: Calling GetLSPs when the server returns unexpected JSON: an array instead of an object, a field type change in LSPClientInfo after a version bump, an empty/garbage body, or a proxy substituting content.

Common situations: Client/server skew after upgrading one side; middlewares (auth proxies, injectors) rewriting responses; server bug emitting partial JSON for many LSPs.

Understand the failure class

Related errors


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