charmbracelet/crush · error

failed to decode LSP diagnostics: %w

Error message

failed to decode LSP diagnostics: %w

What it means

GetLSPDiagnostics got a 200 response but the body could not be decoded into map[protocol.DocumentURI][]protocol.Diagnostic. This indicates the server returned malformed, empty, or unexpected JSON rather than the documented diagnostics shape.

Source

Thrown at internal/client/proto.go:292

		return true
	case <-ctx.Done():
		return false
	}
}

// GetLSPDiagnostics retrieves LSP diagnostics for a specific LSP client.
func (c *Client) GetLSPDiagnostics(ctx context.Context, id string, lspName string) (map[protocol.DocumentURI][]protocol.Diagnostic, error) {
	rsp, err := c.get(ctx, fmt.Sprintf("/workspaces/%s/lsps/%s/diagnostics", id, lspName), nil, nil)
	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)
	}

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Verify client and crush server versions match; upgrade the client if the API schema changed.
  2. Dump the raw body (read rsp.Body into a buffer before decoding) to inspect what was actually returned.
  3. Check for proxies/middleware modifying responses and bypass them.
  4. Retry after restarting the server if the body was truncated.

Example fix

// before
var diagnostics map[protocol.DocumentURI][]protocol.Diagnostic
json.NewDecoder(rsp.Body).Decode(&diagnostics) // opaque error
// after
body, _ := io.ReadAll(rsp.Body)
var diagnostics map[protocol.DocumentURI][]protocol.Diagnostic
if err := json.Unmarshal(body, &diagnostics); err != nil {
    return nil, fmt.Errorf("failed to decode LSP diagnostics: %w (body: %.200s)", err, body)
}
Defensive patterns

Strategy: type-guard

Validate before calling

// Read and sanity-check the body shape before trusting it:
body, _ := io.ReadAll(rsp.Body)
if !bytes.HasPrefix(bytes.TrimSpace(body), []byte("{")) { return errors.New("response is not a JSON object") }

Type guard

func isValidDiagnostics(v map[protocol.DocumentURI][]protocol.Diagnostic) bool {
	for uri, diags := range v {
		if uri == "" { return false }
		for _, d := range diags { if d.Message == "" { return false } }
	}
	return true
}

Try / catch

var diags map[protocol.DocumentURI][]protocol.Diagnostic
if err := json.Unmarshal(body, &diags); err != nil {
	var jerr *json.UnmarshalTypeError
	if errors.As(err, &jerr) {
		return nil, fmt.Errorf("diagnostics schema mismatch at %s: %w", jerr.Field, jerr)
	}
	return nil, fmt.Errorf("malformed diagnostics body: %w", err)
}

Prevention

When it happens

Trigger: Calling GetLSPDiagnostics when the server returns an HTML error page with 200, a truncated body, an incompatible JSON schema (field type mismatch, e.g. a string where an object is expected), or a proxy that rewrites the response.

Common situations: Client and server version mismatch after an upgrade changed the diagnostics schema; an intercepting proxy/captive portal returning HTML; server bug emitting partial output when LSP diagnostics are too large.

Understand the failure class

Related errors


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