multica-ai/multica · error

not a member of this workspace

Error message

not a member of this workspace

What it means

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).

Solutions

  1. Join the workspace first, or connect with an account that is a member.
Defensive patterns

Strategy: validation

When it happens

Trigger: Thrown at server/internal/realtime/hub.go:793 when the library encounters an invalid state.

Common situations: See trigger scenarios.


AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15). Data as JSON: /api/errors/8b0ac6e73980f1ff. Report an issue: GitHub.

Appendix: source

Thrown at server/internal/realtime/hub.go:793

				return
			}
			workspaceID = resolved
		}
	}
	if workspaceID == "" {
		http.Error(w, `{"error":"workspace_id or workspace_slug required"}`, http.StatusBadRequest)
		return
	}

	var userID string
	if cookie, err := r.Cookie(auth.AuthCookieName); err == nil && cookie.Value != "" {
		uid, errMsg := authenticateToken(cookie.Value, pr, r.Context())
		if errMsg != "" {
			http.Error(w, errMsg, http.StatusUnauthorized)
			return
		}
		if !mc.IsMember(r.Context(), uid, workspaceID) {
			http.Error(w, `{"error":"not a member of this workspace"}`, http.StatusForbidden)
			return
		}
		userID = uid
	}

	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		slog.Error("websocket upgrade failed", "error", err)
		return
	}

	// Bound inbound messages here rather than in readPump: the token auth
	// path below reads its first frame before the caller is authenticated, so
	// a limit installed any later leaves that read unbounded.
	conn.SetReadLimit(inboundReadLimit)

	if userID == "" {
		tokenStr, errMsg, closed := firstMessageAuth(conn)

View on GitHub (pinned to 2c0912b6ec)