{"record":{"id":"8b0ac6e73980f1ff","repo":"multica-ai/multica","slug":"not-a-member-of-this-workspace","errorCode":null,"errorMessage":"not a member of this workspace","messagePattern":"not a member of this workspace","errorType":"http","errorClass":null,"httpStatus":403,"severity":"error","filePath":"server/internal/realtime/hub.go","lineNumber":793,"sourceCode":"\t\t\t\treturn\n\t\t\t}\n\t\t\tworkspaceID = resolved\n\t\t}\n\t}\n\tif workspaceID == \"\" {\n\t\thttp.Error(w, `{\"error\":\"workspace_id or workspace_slug required\"}`, http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tvar userID string\n\tif cookie, err := r.Cookie(auth.AuthCookieName); err == nil && cookie.Value != \"\" {\n\t\tuid, errMsg := authenticateToken(cookie.Value, pr, r.Context())\n\t\tif errMsg != \"\" {\n\t\t\thttp.Error(w, errMsg, http.StatusUnauthorized)\n\t\t\treturn\n\t\t}\n\t\tif !mc.IsMember(r.Context(), uid, workspaceID) {\n\t\t\thttp.Error(w, `{\"error\":\"not a member of this workspace\"}`, http.StatusForbidden)\n\t\t\treturn\n\t\t}\n\t\tuserID = uid\n\t}\n\n\tconn, err := upgrader.Upgrade(w, r, nil)\n\tif err != nil {\n\t\tslog.Error(\"websocket upgrade failed\", \"error\", err)\n\t\treturn\n\t}\n\n\t// Bound inbound messages here rather than in readPump: the token auth\n\t// path below reads its first frame before the caller is authenticated, so\n\t// a limit installed any later leaves that read unbounded.\n\tconn.SetReadLimit(inboundReadLimit)\n\n\tif userID == \"\" {\n\t\ttokenStr, errMsg, closed := firstMessageAuth(conn)","sourceCodeStart":775,"sourceCodeEnd":811,"githubUrl":"https://github.com/multica-ai/multica/blob/2c0912b6ec764b373d44eeea1e80f0d9f11ab417/server/internal/realtime/hub.go#L775-L811","documentation":"Generic membership guard in HandleWebSocket's HTTP upgrade path: after authenticating the session cookie via authenticateToken, the server checks mc.IsMember for the requested workspace before issuing the WebSocket connection. It returns this sentinel 403 body when the authenticated userID has no membership record for the workspace identified by workspace_id/workspace_slug — i.e. a valid, logged-in user attempting to open a realtime socket for a workspace they do not belong to (wrong workspace, revoked/removed membership, or a slug pointing at another org's workspace).","triggerScenarios":"Thrown at server/internal/realtime/hub.go:793 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Join the workspace first, or connect with an account that is a member."],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"2c0912b6ec764b373d44eeea1e80f0d9f11ab417","analyzedAt":"2026-08-15T13:25:18.241Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}