{"record":{"id":"4847a4d10e7b4153","repo":"charmbracelet/crush","slug":"failed-to-decode-messages-w","errorCode":null,"errorMessage":"failed to decode messages: %w","messagePattern":"failed to decode messages: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/client/proto.go","lineNumber":602,"sourceCode":"\tif rsp.StatusCode != http.StatusOK {\n\t\treturn fmt.Errorf(\"failed to initiate session agent processing: status code %d\", rsp.StatusCode)\n\t}\n\treturn nil\n}\n\n// ListMessages retrieves all messages for a session as proto types.\nfunc (c *Client) ListMessages(ctx context.Context, id string, sessionID string) ([]proto.Message, error) {\n\trsp, err := c.get(ctx, fmt.Sprintf(\"/workspaces/%s/sessions/%s/messages\", id, sessionID), nil, nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get messages: %w\", err)\n\t}\n\tdefer rsp.Body.Close()\n\tif rsp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"failed to get messages: status code %d\", rsp.StatusCode)\n\t}\n\tvar msgs []proto.Message\n\tif err := json.NewDecoder(rsp.Body).Decode(&msgs); err != nil && !errors.Is(err, io.EOF) {\n\t\treturn nil, fmt.Errorf(\"failed to decode messages: %w\", err)\n\t}\n\treturn msgs, nil\n}\n\n// GetSession retrieves a specific session as a proto type.\nfunc (c *Client) GetSession(ctx context.Context, id string, sessionID string) (*proto.Session, error) {\n\trsp, err := c.get(ctx, fmt.Sprintf(\"/workspaces/%s/sessions/%s\", id, sessionID), nil, nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get session: %w\", err)\n\t}\n\tdefer rsp.Body.Close()\n\tif rsp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"failed to get session: status code %d\", rsp.StatusCode)\n\t}\n\tvar sess proto.Session\n\tif err := json.NewDecoder(rsp.Body).Decode(&sess); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to decode session: %w\", err)\n\t}","sourceCodeStart":584,"sourceCodeEnd":620,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/client/proto.go#L584-L620","documentation":"Thrown by Client.ListMessages when the 200 response body from GET /workspaces/{id}/sessions/{sessionID}/messages cannot be JSON-decoded into []proto.Message. Unlike GetSessionInfo, an empty body (io.EOF) is tolerated, so this error means the body was non-empty but malformed or structurally incompatible with proto.Message. Called via restoreModelFromSession.","triggerScenarios":"Server returns 200 with a corrupted/truncated messages payload; a proxy rewrites the body (e.g. HTML error injected); server schema change where a field type no longer matches proto.Message (json.UnmarshalTypeError); binary garbage instead of JSON.","commonSituations":"Client/server version skew after an upgrade changed the message schema; middlebox (antivirus/proxy) mangling responses; DB rows written by an older version that no longer unmarshal into the current proto.Message type; disk corruption on the server.","solutions":["Use errors.As to extract json.UnmarshalTypeError and identify which field of proto.Message is incompatible.","Align client and server versions so the Message JSON schema matches.","Bypass any proxy/middlebox and curl the endpoint directly to see the raw body.","If old rows are the cause, migrate or discard the incompatible session data on the server."],"exampleFix":"// before\nmsgs, err := client.ListMessages(ctx, wsID, sessionID)\nif err != nil {\n    return err\n}\n// after: narrow down the incompatible field\nmsgs, err := client.ListMessages(ctx, wsID, sessionID)\nif err != nil {\n    var ute *json.UnmarshalTypeError\n    if errors.As(err, &ute) {\n        return fmt.Errorf(\"message field %q has wrong type (version skew?): %w\", ute.Field, err)\n    }\n    return err\n}","handlingStrategy":"type-guard","validationCode":"// Probe the endpoint and verify the body parses as a message array\nrsp, err := http.Get(serverURL + \"/workspaces/\" + wsID + \"/sessions/\" + sessionID + \"/messages\")\nif err == nil && rsp.StatusCode == 200 {\n    var probe []json.RawMessage\n    if json.NewDecoder(rsp.Body).Decode(&probe) != nil {\n        return errors.New(\"server returned malformed messages payload — check version skew\")\n    }\n    rsp.Body.Close()\n}","typeGuard":"func isMessageSchemaError(err error) (string, bool) {\n    var ute *json.UnmarshalTypeError\n    if errors.As(err, &ute) {\n        return ute.Field, true\n    }\n    return \"\", false\n}","tryCatchPattern":"msgs, err := client.ListMessages(ctx, wsID, sessionID)\nif err != nil {\n    if field, ok := isMessageSchemaError(err); ok {\n        return fmt.Errorf(\"incompatible message schema at field %q — upgrade client/server to matching versions\", field)\n    }\n    return err\n}","preventionTips":["Keep client and server builds in lockstep — proto.Message field types are the contract","Extract the json.UnmarshalTypeError to identify the offending field","Test restore paths against both old-format and new-format session data","Bypass proxies/antivirus when diagnosing persistent decode failures"],"tags":["json","decoding","messages","schema"],"backgroundTag":"json-decode-failed","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}