{"record":{"id":"d7ebddaa3a09b8e2","repo":"charmbracelet/crush","slug":"failed-to-decode-sessions-w","errorCode":null,"errorMessage":"failed to decode sessions: %w","messagePattern":"failed to decode sessions: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/client/proto.go","lineNumber":670,"sourceCode":"\tif err := json.NewDecoder(rsp.Body).Decode(&sess); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to decode session: %w\", err)\n\t}\n\treturn &sess, nil\n}\n\n// ListSessions lists all sessions in a workspace as proto types.\nfunc (c *Client) ListSessions(ctx context.Context, id string) ([]proto.Session, error) {\n\trsp, err := c.get(ctx, fmt.Sprintf(\"/workspaces/%s/sessions\", id), nil, nil)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to get sessions: %w\", err)\n\t}\n\tdefer rsp.Body.Close()\n\tif rsp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"failed to get sessions: status code %d\", rsp.StatusCode)\n\t}\n\tvar sessions []proto.Session\n\tif err := json.NewDecoder(rsp.Body).Decode(&sessions); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to decode sessions: %w\", err)\n\t}\n\treturn sessions, nil\n}\n\n// GrantPermission grants a permission on a workspace. The returned\n// bool reports whether this call resolved the pending request (true)\n// or found it already resolved by a previous caller (false). A false\n// value is not an error — it just means another subscriber resolved\n// the same request first.\nfunc (c *Client) GrantPermission(ctx context.Context, id string, req proto.PermissionGrant) (bool, error) {\n\trsp, err := c.post(ctx, fmt.Sprintf(\"/workspaces/%s/permissions/grant\", id), nil, jsonBody(req), http.Header{\"Content-Type\": []string{\"application/json\"}})\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to grant permission: %w\", err)\n\t}\n\tdefer rsp.Body.Close()\n\tif rsp.StatusCode != http.StatusOK {\n\t\treturn false, fmt.Errorf(\"failed to grant permission: status code %d\", rsp.StatusCode)\n\t}","sourceCodeStart":652,"sourceCodeEnd":688,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/client/proto.go#L652-L688","documentation":"ListSessions received HTTP 200 but the body could not be decoded into []proto.Session. The JSON decode error is wrapped with this message. The response was not a valid JSON array of sessions with the expected field types.","triggerScenarios":"Sessions endpoint returns 200 with a JSON object instead of an array, a truncated body, an empty body, or Session fields whose types changed between versions.","commonSituations":"Proxy returning 200 with an HTML page; server/client schema skew on Session; server bug emitting null instead of an array.","solutions":["Log the wrapped decode error for the exact cause.","curl the endpoint and inspect the raw JSON shape.","Align server and client versions on the Session schema.","Check proxies/middleware rewriting responses.","Retry on truncated responses."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":"func isSessionsDecodeError(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"failed to decode sessions\")\n}","tryCatchPattern":"sessions, err := client.ListSessions(ctx, wsID)\nif err != nil {\n    if isSessionsDecodeError(err) {\n        // 200 with bad body: align schema versions and retry\n        return fmt.Errorf(\"unparseable sessions payload: %w\", err)\n    }\n    return err\n}","preventionTips":["Keep server and client Session schemas in sync.","Inspect raw endpoint output with curl during integration.","Watch for proxies returning 200 with non-JSON bodies.","Retry on truncation-style decode errors."],"tags":["http","json-decode","client"],"backgroundTag":"json-response-decode-failed","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}