{"record":{"id":"30a9d9c7964a8354","repo":"charmbracelet/crush","slug":"failed-to-decode-lsp-diagnostics-w","errorCode":null,"errorMessage":"failed to decode LSP diagnostics: %w","messagePattern":"failed to decode LSP diagnostics: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/client/proto.go","lineNumber":292,"sourceCode":"\t\treturn true\n\tcase <-ctx.Done():\n\t\treturn false\n\t}\n}\n\n// GetLSPDiagnostics retrieves LSP diagnostics for a specific LSP client.\nfunc (c *Client) GetLSPDiagnostics(ctx context.Context, id string, lspName string) (map[protocol.DocumentURI][]protocol.Diagnostic, error) {\n\trsp, err := c.get(ctx, fmt.Sprintf(\"/workspaces/%s/lsps/%s/diagnostics\", id, lspName), nil, nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get LSP diagnostics: %w\", err)\n\t}\n\tdefer rsp.Body.Close()\n\tif rsp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"failed to get LSP diagnostics: status code %d\", rsp.StatusCode)\n\t}\n\tvar diagnostics map[protocol.DocumentURI][]protocol.Diagnostic\n\tif err := json.NewDecoder(rsp.Body).Decode(&diagnostics); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to decode LSP diagnostics: %w\", err)\n\t}\n\treturn diagnostics, nil\n}\n\n// GetLSPs retrieves the LSP client states for a workspace.\nfunc (c *Client) GetLSPs(ctx context.Context, id string) (map[string]proto.LSPClientInfo, error) {\n\trsp, err := c.get(ctx, fmt.Sprintf(\"/workspaces/%s/lsps\", id), nil, nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get LSPs: %w\", err)\n\t}\n\tdefer rsp.Body.Close()\n\tif rsp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"failed to get LSPs: status code %d\", rsp.StatusCode)\n\t}\n\tvar lsps map[string]proto.LSPClientInfo\n\tif err := json.NewDecoder(rsp.Body).Decode(&lsps); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to decode LSPs: %w\", err)\n\t}","sourceCodeStart":274,"sourceCodeEnd":310,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/client/proto.go#L274-L310","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Verify client and crush server versions match; upgrade the client if the API schema changed.","Dump the raw body (read rsp.Body into a buffer before decoding) to inspect what was actually returned.","Check for proxies/middleware modifying responses and bypass them.","Retry after restarting the server if the body was truncated."],"exampleFix":"// before\nvar diagnostics map[protocol.DocumentURI][]protocol.Diagnostic\njson.NewDecoder(rsp.Body).Decode(&diagnostics) // opaque error\n// after\nbody, _ := io.ReadAll(rsp.Body)\nvar diagnostics map[protocol.DocumentURI][]protocol.Diagnostic\nif err := json.Unmarshal(body, &diagnostics); err != nil {\n    return nil, fmt.Errorf(\"failed to decode LSP diagnostics: %w (body: %.200s)\", err, body)\n}","handlingStrategy":"type-guard","validationCode":"// Read and sanity-check the body shape before trusting it:\nbody, _ := io.ReadAll(rsp.Body)\nif !bytes.HasPrefix(bytes.TrimSpace(body), []byte(\"{\")) { return errors.New(\"response is not a JSON object\") }","typeGuard":"func isValidDiagnostics(v map[protocol.DocumentURI][]protocol.Diagnostic) bool {\n\tfor uri, diags := range v {\n\t\tif uri == \"\" { return false }\n\t\tfor _, d := range diags { if d.Message == \"\" { return false } }\n\t}\n\treturn true\n}","tryCatchPattern":"var diags map[protocol.DocumentURI][]protocol.Diagnostic\nif err := json.Unmarshal(body, &diags); err != nil {\n\tvar jerr *json.UnmarshalTypeError\n\tif errors.As(err, &jerr) {\n\t\treturn nil, fmt.Errorf(\"diagnostics schema mismatch at %s: %w\", jerr.Field, jerr)\n\t}\n\treturn nil, fmt.Errorf(\"malformed diagnostics body: %w\", err)\n}","preventionTips":["Pin matching client/server versions to avoid schema drift.","Log raw bodies on decode failure for fast diagnosis.","Check Content-Type is application/json before decoding.","Watch for proxies/captive portals injecting HTML into 200 responses."],"tags":["json","decode","lsp","go"],"backgroundTag":"json-decode-failed","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}