diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,87 @@
 # Changelog for haskell-language-server
 
+## 2.6.0.0
+
+- Bindists for GHC 9.6.4
+- A new semantic tokens plugin (#3892, @soulomoon).
+- Improvements to multiple home unit support with GHC 9.4. Using cabal 3.11+ will
+  load proper multiple home unit sessions by default, fixing a lot of issues with
+  loading and reloading projects that have more than one component (#3462, @wz1000).
+- Removed implicit-hie, resulting in better behaviour for projects without cradles.
+- Don't produce diagnostics for disabled plugins (#3941, @fendor).
+- Many other bug fixes.
+
+### Pull Requests
+
+- fix: semantic token omitting record field in `{-# LANGUAGE DuplicateRecordFields  #-}` #3950
+  ([#3951](https://github.com/haskell/haskell-language-server/pull/3951)) by @soulomoon
+- Properties API: Remove unsafe coerce in favor of type class based method in 
+  ([#3947](https://github.com/haskell/haskell-language-server/pull/3947)) by @soulomoon
+- Bump to hiedb 0.5.0.0 to fix #3542
+  ([#3943](https://github.com/haskell/haskell-language-server/pull/3943)) by @wz1000
+- Don't produce diagnostics if plugin is turned off
+  ([#3941](https://github.com/haskell/haskell-language-server/pull/3941)) by @fendor
+- add config  for semantic-tokens-plugin for mapping from hs token type to LSP default token type
+  ([#3940](https://github.com/haskell/haskell-language-server/pull/3940)) by @soulomoon
+- add doc and ci test for semantic tokens
+  ([#3938](https://github.com/haskell/haskell-language-server/pull/3938)) by @soulomoon
+- update Floskell to 0.11.*
+  ([#3933](https://github.com/haskell/haskell-language-server/pull/3933)) by @peterbecich
+- Remove some people from CODEOWNERS
+  ([#3930](https://github.com/haskell/haskell-language-server/pull/3930)) by @michaelpj
+- Adapt to minor API change for 9.6.4 compatibility
+  ([#3929](https://github.com/haskell/haskell-language-server/pull/3929)) by @wz1000
+- Fix multi unit session when some packages have reexported modules.
+  ([#3928](https://github.com/haskell/haskell-language-server/pull/3928)) by @wz1000
+- Switch to haskell-actions/setup since haskell/actions is deprecated
+  ([#3926](https://github.com/haskell/haskell-language-server/pull/3926)) by @fendor
+- Make vscode-extension-schema honour default values
+  ([#3925](https://github.com/haskell/haskell-language-server/pull/3925)) by @fendor
+- Add golden tests for public configs
+  ([#3922](https://github.com/haskell/haskell-language-server/pull/3922)) by @fendor
+- Bump geekyeggo/delete-artifact from 2 to 4
+  ([#3921](https://github.com/haskell/haskell-language-server/pull/3921)) by @dependabot[bot]
+- Fix positionMapping in stale data
+  ([#3920](https://github.com/haskell/haskell-language-server/pull/3920)) by @soulomoon
+- Disable stan plugin by default
+  ([#3917](https://github.com/haskell/haskell-language-server/pull/3917)) by @fendor
+- Use stan config files for stan plugin (#3904)
+  ([#3914](https://github.com/haskell/haskell-language-server/pull/3914)) by @0rphee
+- Bump both upload and download artifact
+  ([#3913](https://github.com/haskell/haskell-language-server/pull/3913)) by @michaelpj
+- Update ghc-version-support.md for 2.5.0
+  ([#3909](https://github.com/haskell/haskell-language-server/pull/3909)) by @lehmacdj
+- Give plugins descriptions, include versions of key dependencies 
+  ([#3903](https://github.com/haskell/haskell-language-server/pull/3903)) by @michaelpj
+- Remove some buildability blockers that aren't needed
+  ([#3899](https://github.com/haskell/haskell-language-server/pull/3899)) by @michaelpj
+- Bump actions/setup-python from 4 to 5
+  ([#3895](https://github.com/haskell/haskell-language-server/pull/3895)) by @dependabot[bot]
+- Update index-state to get latest stan version
+  ([#3894](https://github.com/haskell/haskell-language-server/pull/3894)) by @0rphee
+- Generate FileTarget for all possible targetLocations
+  ([#3893](https://github.com/haskell/haskell-language-server/pull/3893)) by @fendor
+- Implement semantic tokens plugin to support semantic highlighting(textDocument/semanticTokens/full)
+  ([#3892](https://github.com/haskell/haskell-language-server/pull/3892)) by @soulomoon
+- session-loader: Set working directory on GHC 9.4+
+  ([#3891](https://github.com/haskell/haskell-language-server/pull/3891)) by @wz1000
+- Demote home unit closure errors to warnings.
+  ([#3890](https://github.com/haskell/haskell-language-server/pull/3890)) by @wz1000
+- Bump cachix/install-nix-action from 23 to 24
+  ([#3889](https://github.com/haskell/haskell-language-server/pull/3889)) by @dependabot[bot]
+- Bump cachix/cachix-action from 12 to 13
+  ([#3888](https://github.com/haskell/haskell-language-server/pull/3888)) by @dependabot[bot]
+- Add more docs for implicit discovery
+  ([#3887](https://github.com/haskell/haskell-language-server/pull/3887)) by @fendor
+- Prepare release 2.5.0.0
+  ([#3879](https://github.com/haskell/haskell-language-server/pull/3879)) by @wz1000
+- Improve no plugin messages
+  ([#3864](https://github.com/haskell/haskell-language-server/pull/3864)) by @joyfulmantis
+- Add support for multi unit argument syntax
+  ([#3462](https://github.com/haskell/haskell-language-server/pull/3462)) by @wz1000
+- Fix completion for qualified import
+  ([#2838](https://github.com/haskell/haskell-language-server/pull/2838)) by @xsebek
+
 ## 2.5.0.0
 
 - Bindists for GHC 9.4.8
diff --git a/exe/Main.hs b/exe/Main.hs
--- a/exe/Main.hs
+++ b/exe/Main.hs
@@ -13,7 +13,11 @@
 import           Data.Functor                  ((<&>))
 import           Data.Maybe                    (catMaybes)
 import           Data.Text                     (Text)
-import           Ide.Logger  (Doc, Priority (Error, Info),
+import qualified HlsPlugins                    as Plugins
+import           Ide.Arguments                 (Arguments (..),
+                                                GhcideArguments (..),
+                                                getArguments)
+import           Ide.Logger                    (Doc, Priority (Error, Info),
                                                 Recorder,
                                                 WithPriority (WithPriority, priority),
                                                 cfilter, cmapWithPrio,
@@ -21,11 +25,7 @@
                                                 layoutPretty, logWith,
                                                 makeDefaultStderrRecorder,
                                                 renderStrict, withFileRecorder)
-import qualified Ide.Logger  as Logger
-import qualified HlsPlugins                    as Plugins
-import           Ide.Arguments                 (Arguments (..),
-                                                GhcideArguments (..),
-                                                getArguments)
+import qualified Ide.Logger                    as Logger
 import           Ide.Main                      (defaultMain)
 import qualified Ide.Main                      as IdeMain
 import           Ide.PluginUtils               (pluginDescToIdePlugins)
@@ -70,7 +70,7 @@
                 ])
     -- This plugin just installs a handler for the `initialized` notification, which then
     -- picks up the LSP environment and feeds it to our recorders
-    let lspRecorderPlugin = (defaultPluginDescriptor "LSPRecorderCallback")
+    let lspRecorderPlugin = (defaultPluginDescriptor "LSPRecorderCallback" "Internal plugin")
           { pluginNotificationHandlers = mkPluginNotificationHandler LSP.SMethod_Initialized $ \_ _ _ _ -> do
               env <- LSP.getLspEnv
               liftIO $ (cb1 <> cb2) env
diff --git a/exe/Wrapper.hs b/exe/Wrapper.hs
--- a/exe/Wrapper.hs
+++ b/exe/Wrapper.hs
@@ -51,8 +51,7 @@
                                                      Recorder (logger_),
                                                      WithPriority (WithPriority),
                                                      cmapWithPrio,
-                                                     makeDefaultStderrRecorder,
-                                                     toCologActionWithPrio)
+                                                     makeDefaultStderrRecorder)
 import           Ide.Plugin.Config                  (Config)
 import           Ide.Types                          (IdePlugins (IdePlugins))
 import           Language.LSP.Protocol.Message      (Method (Method_Initialize),
@@ -83,8 +82,8 @@
           putStrLn "Tool versions found on the $PATH"
           putStrLn $ showProgramVersionOfInterest programsOfInterest
           putStrLn "Tool versions in your project"
-          cradle <- findProjectCradle' False
-          ghcVersion <- runExceptT $ getRuntimeGhcVersion' recorder cradle
+          cradle <- findProjectCradle' recorder False
+          ghcVersion <- runExceptT $ getRuntimeGhcVersion' cradle
           putStrLn $ showProgramVersion "ghc" $ mkVersion =<< eitherToMaybe ghcVersion
 
       VersionMode PrintVersion ->
@@ -94,10 +93,10 @@
           putStrLn haskellLanguageServerNumericVersion
 
       BiosMode PrintCradleType ->
-          print =<< findProjectCradle
+          print =<< findProjectCradle recorder
       PrintLibDir -> do
-          cradle <- findProjectCradle' False
-          (CradleSuccess libdir) <- HieBios.getRuntimeGhcLibDir (toCologActionWithPrio (cmapWithPrio pretty recorder)) cradle
+          cradle <- findProjectCradle' recorder False
+          (CradleSuccess libdir) <- HieBios.getRuntimeGhcLibDir cradle
           putStr libdir
       _ -> launchHaskellLanguageServer recorder args >>= \case
             Right () -> pure ()
@@ -116,7 +115,7 @@
   d <- getCurrentDirectory
 
   -- search for the project cradle type
-  cradle <- findProjectCradle
+  cradle <- findProjectCradle recorder
 
   -- Get the root directory from the cradle
   setCurrentDirectory $ cradleRootDir cradle
@@ -124,7 +123,7 @@
   case parsedArgs of
     Ghcide GhcideArguments{..} ->
       when argsProjectGhcVersion $ do
-        runExceptT (getRuntimeGhcVersion' recorder cradle) >>= \case
+        runExceptT (getRuntimeGhcVersion' cradle) >>= \case
           Right ghcVersion -> putStrLn ghcVersion >> exitSuccess
           Left err -> T.putStrLn (prettyError err NoShorten) >> exitFailure
     _ -> pure ()
@@ -147,7 +146,7 @@
   hPutStrLn stderr "Consulting the cradle to get project GHC version..."
 
   runExceptT $ do
-      ghcVersion <- getRuntimeGhcVersion' recorder cradle
+      ghcVersion <- getRuntimeGhcVersion' cradle
       liftIO $ hPutStrLn stderr $ "Project GHC version: " ++ ghcVersion
 
       let
@@ -172,10 +171,10 @@
 
           let cradleName = actionName (cradleOptsProg cradle)
           -- we need to be compatible with NoImplicitPrelude
-          ghcBinary <- liftIO (fmap trim <$> runGhcCmd (toCologActionWithPrio (cmapWithPrio pretty recorder)) ["-v0", "-package-env=-", "-ignore-dot-ghci", "-e", "Control.Monad.join (Control.Monad.fmap System.IO.putStr System.Environment.getExecutablePath)"])
+          ghcBinary <- liftIO (fmap trim <$> runGhcCmd ["-v0", "-package-env=-", "-ignore-dot-ghci", "-e", "Control.Monad.join (Control.Monad.fmap System.IO.putStr System.Environment.getExecutablePath)"])
                          >>= cradleResult cradleName
 
-          libdir <- liftIO (HieBios.getRuntimeGhcLibDir (toCologActionWithPrio (cmapWithPrio pretty recorder)) cradle)
+          libdir <- liftIO (HieBios.getRuntimeGhcLibDir cradle)
                       >>= cradleResult cradleName
 
           env <- Map.fromList <$> liftIO getEnvironment
@@ -192,8 +191,8 @@
 
 -- | Version of 'getRuntimeGhcVersion' that dies if we can't get it, and also
 -- checks to see if the tool is missing if it is one of
-getRuntimeGhcVersion' :: Recorder (WithPriority (Doc ())) -> Cradle Void -> ExceptT WrapperSetupError IO String
-getRuntimeGhcVersion' recorder cradle = do
+getRuntimeGhcVersion' :: Cradle Void -> ExceptT WrapperSetupError IO String
+getRuntimeGhcVersion' cradle = do
   let cradleName = actionName (cradleOptsProg cradle)
 
   -- See if the tool is installed
@@ -204,7 +203,7 @@
     Direct  -> checkToolExists "ghc"
     _       -> pure ()
 
-  ghcVersionRes <- liftIO $ HieBios.getRuntimeGhcVersion (toCologActionWithPrio (cmapWithPrio pretty recorder)) cradle
+  ghcVersionRes <- liftIO $ HieBios.getRuntimeGhcVersion cradle
   cradleResult cradleName ghcVersionRes
 
   where
@@ -214,11 +213,11 @@
         Just _ -> pure ()
         Nothing -> throwE $ ToolRequirementMissing exe (actionName (cradleOptsProg cradle))
 
-findProjectCradle :: IO (Cradle Void)
-findProjectCradle = findProjectCradle' True
+findProjectCradle :: Recorder (WithPriority (Doc ())) -> IO (Cradle Void)
+findProjectCradle recorder = findProjectCradle' recorder True
 
-findProjectCradle' :: Bool -> IO (Cradle Void)
-findProjectCradle' log = do
+findProjectCradle' :: Recorder (WithPriority (Doc ())) -> Bool -> IO (Cradle Void)
+findProjectCradle' recorder log = do
   d <- getCurrentDirectory
 
   let initialFp = d </> "a"
@@ -230,7 +229,7 @@
         Just yaml -> hPutStrLn stderr $ "Found \"" ++ yaml ++ "\" for \"" ++ initialFp ++ "\""
         Nothing -> hPutStrLn stderr "No 'hie.yaml' found. Try to discover the project type!"
 
-  Session.loadCradle def hieYaml d
+  Session.loadCradle def (cmapWithPrio pretty recorder) hieYaml d
 
 trim :: String -> String
 trim s = case lines s of
diff --git a/haskell-language-server.cabal b/haskell-language-server.cabal
--- a/haskell-language-server.cabal
+++ b/haskell-language-server.cabal
@@ -1,7 +1,7 @@
 cabal-version:      3.0
 category:           Development
 name:               haskell-language-server
-version:            2.5.0.0
+version:            2.6.0.0
 synopsis:           LSP server for GHC
 description:
   Please see the README on GitHub at <https://github.com/haskell/haskell-language-server#readme>
@@ -164,6 +164,11 @@
   default:     True
   manual:      True
 
+flag semanticTokens
+  description: Enable semantic tokens plugin
+  default:     True
+  manual:      True
+
 -- formatters
 
 flag floskell
@@ -203,136 +208,142 @@
 
 common cabalfmt
   if flag(cabalfmt)
-    build-depends: hls-cabal-fmt-plugin == 2.5.0.0
+    build-depends: hls-cabal-fmt-plugin == 2.6.0.0
     cpp-options: -Dhls_cabalfmt
 
 common cabal
   if flag(cabal)
-    build-depends: hls-cabal-plugin == 2.5.0.0
+    build-depends: hls-cabal-plugin == 2.6.0.0
     cpp-options: -Dhls_cabal
 
 common class
   if flag(class) && (impl(ghc < 9.8.0) || flag(ignore-plugins-ghc-bounds))
-    build-depends: hls-class-plugin == 2.5.0.0
+    build-depends: hls-class-plugin == 2.6.0.0
     cpp-options: -Dhls_class
 
 common callHierarchy
   if flag(callHierarchy)
-    build-depends: hls-call-hierarchy-plugin == 2.5.0.0
+    build-depends: hls-call-hierarchy-plugin == 2.6.0.0
     cpp-options: -Dhls_callHierarchy
 
 common eval
   if flag(eval)
-    build-depends: hls-eval-plugin == 2.5.0.0
+    build-depends: hls-eval-plugin == 2.6.0.0
     cpp-options: -Dhls_eval
 
 common importLens
   if flag(importLens)
-    build-depends: hls-explicit-imports-plugin == 2.5.0.0
+    build-depends: hls-explicit-imports-plugin == 2.6.0.0
     cpp-options: -Dhls_importLens
 
 common rename
   if flag(rename) && (impl(ghc < 9.8.0) || flag(ignore-plugins-ghc-bounds))
-    build-depends: hls-rename-plugin == 2.5.0.0
+    build-depends: hls-rename-plugin == 2.6.0.0
     cpp-options: -Dhls_rename
 
 common retrie
   if flag(retrie) && (impl(ghc < 9.8.0) || flag(ignore-plugins-ghc-bounds))
-    build-depends: hls-retrie-plugin == 2.5.0.0
+    build-depends: hls-retrie-plugin == 2.6.0.0
     cpp-options: -Dhls_retrie
 
 common hlint
   if flag(hlint) && (impl(ghc < 9.8.0) || flag(ignore-plugins-ghc-bounds))
-    build-depends: hls-hlint-plugin == 2.5.0.0
+    build-depends: hls-hlint-plugin == 2.6.0.0
     cpp-options: -Dhls_hlint
 
 common stan
   if flag(stan) && (impl(ghc > 8.8.1) && impl(ghc <= 9.2.3) || impl(ghc >= 9.4.0) && impl(ghc < 9.10.0))
-    build-depends: hls-stan-plugin == 2.5.0.0
+    build-depends: hls-stan-plugin == 2.6.0.0
     cpp-options: -Dhls_stan
 
 common moduleName
   if flag(moduleName)
-    build-depends: hls-module-name-plugin == 2.5.0.0
+    build-depends: hls-module-name-plugin == 2.6.0.0
     cpp-options: -Dhls_moduleName
 
 common pragmas
   if flag(pragmas)
-    build-depends: hls-pragmas-plugin == 2.5.0.0
+    build-depends: hls-pragmas-plugin == 2.6.0.0
     cpp-options: -Dhls_pragmas
 
 common splice
   if flag(splice) && (impl(ghc < 9.8.0) || flag(ignore-plugins-ghc-bounds))
-    build-depends: hls-splice-plugin == 2.5.0.0
+    build-depends: hls-splice-plugin == 2.6.0.0
     cpp-options: -Dhls_splice
 
 common alternateNumberFormat
   if flag(alternateNumberFormat)
-    build-depends: hls-alternate-number-format-plugin == 2.5.0.0
+    build-depends: hls-alternate-number-format-plugin == 2.6.0.0
     cpp-options: -Dhls_alternateNumberFormat
 
 common qualifyImportedNames
   if flag(qualifyImportedNames)
-    build-depends: hls-qualify-imported-names-plugin == 2.5.0.0
+    build-depends: hls-qualify-imported-names-plugin == 2.6.0.0
     cpp-options: -Dhls_qualifyImportedNames
 
 common codeRange
   if flag(codeRange)
-    build-depends: hls-code-range-plugin == 2.5.0.0
+    build-depends: hls-code-range-plugin == 2.6.0.0
     cpp-options: -Dhls_codeRange
 
 common changeTypeSignature
   if flag(changeTypeSignature)
-    build-depends: hls-change-type-signature-plugin == 2.5.0.0
+    build-depends: hls-change-type-signature-plugin == 2.6.0.0
     cpp-options: -Dhls_changeTypeSignature
 
 common gadt
   if flag(gadt) && (impl(ghc < 9.8.0) || flag(ignore-plugins-ghc-bounds))
-    build-depends: hls-gadt-plugin == 2.5.0.0
+    build-depends: hls-gadt-plugin == 2.6.0.0
     cpp-options: -Dhls_gadt
 
 common explicitFixity
   if flag(explicitFixity)
-    build-depends: hls-explicit-fixity-plugin == 2.5.0.0
+    build-depends: hls-explicit-fixity-plugin == 2.6.0.0
     cpp-options: -DexplicitFixity
 
 common explicitFields
   if flag(explicitFields)
-    build-depends: hls-explicit-record-fields-plugin == 2.5.0.0
+    build-depends: hls-explicit-record-fields-plugin == 2.6.0.0
     cpp-options: -DexplicitFields
 
 common overloadedRecordDot
   if flag(overloadedRecordDot) && (impl(ghc >= 9.2.0) || flag(ignore-plugins-ghc-bounds))
-    build-depends: hls-overloaded-record-dot-plugin == 2.5.0.0
+    build-depends: hls-overloaded-record-dot-plugin == 2.6.0.0
     cpp-options: -Dhls_overloaded_record_dot
 
 -- formatters
 
 common floskell
-  if flag(floskell) && impl(ghc < 9.5)
-    build-depends: hls-floskell-plugin == 2.5.0.0
+  if flag(floskell) && (impl(ghc < 9.7) || flag(ignore-plugins-ghc-bounds))
+    build-depends: hls-floskell-plugin == 2.6.0.0
     cpp-options: -Dhls_floskell
 
 common fourmolu
-  if flag(fourmolu) && (impl(ghc < 9.8.0) || flag(ignore-plugins-ghc-bounds))
-    build-depends: hls-fourmolu-plugin == 2.5.0.0
+  if flag(fourmolu)
+    build-depends: hls-fourmolu-plugin == 2.6.0.0
     cpp-options: -Dhls_fourmolu
 
 common ormolu
-  if flag(ormolu) && impl(ghc < 9.7)
-    build-depends: hls-ormolu-plugin == 2.5.0.0
+  if flag(ormolu)
+    build-depends: hls-ormolu-plugin == 2.6.0.0
     cpp-options: -Dhls_ormolu
 
 common stylishHaskell
   if flag(stylishHaskell) && (impl(ghc < 9.8.0) || flag(ignore-plugins-ghc-bounds))
-    build-depends: hls-stylish-haskell-plugin == 2.5.0.0
+    build-depends: hls-stylish-haskell-plugin == 2.6.0.0
     cpp-options: -Dhls_stylishHaskell
 
 common refactor
   if flag(refactor) && (impl(ghc < 9.8.0) || flag(ignore-plugins-ghc-bounds))
-    build-depends: hls-refactor-plugin == 2.5.0.0
+    build-depends: hls-refactor-plugin == 2.6.0.0
     cpp-options: -Dhls_refactor
 
+common semanticTokens
+  if flag(semanticTokens)
+    build-depends: hls-semantic-tokens-plugin == 2.6.0.0
+    cpp-options: -Dhls_semanticTokens
+
+
 library
   import:           common-deps
                     -- configuration
@@ -365,6 +376,7 @@
                   , stylishHaskell
                   , refactor
                   , overloadedRecordDot
+                  , semanticTokens
 
   exposed-modules:
     Ide.Arguments
@@ -383,12 +395,12 @@
     , cryptohash-sha1
     , data-default
     , ghc
-    , ghcide                == 2.5.0.0
+    , ghcide                == 2.6.0.0
     , githash               >=0.1.6.1
     , lsp                   >= 2.3.0.0
     , hie-bios
     , hiedb
-    , hls-plugin-api        == 2.5.0.0
+    , hls-plugin-api        == 2.6.0.0
     , optparse-applicative
     , optparse-simple
     , process
@@ -522,12 +534,11 @@
     , data-default
     , deepseq
     , hashable
-    , hspec-expectations
     , lens
     , lens-aeson
     , ghcide
     , ghcide-test-utils
-    , hls-test-utils == 2.5.0.0
+    , hls-test-utils == 2.6.0.0
     , lsp-types
     , aeson
     , hls-plugin-api
@@ -541,6 +552,7 @@
   main-is:            Main.hs
   other-modules:
     Config
+    ConfigSchema
     Format
     FunctionalBadProject
     HieBios
@@ -556,7 +568,7 @@
   if flag(eval)
     cpp-options: -Dhls_eval
 -- formatters
-  if flag(floskell) && (impl(ghc < 9.2.1) || flag(ignore-plugins-ghc-bounds))
+  if flag(floskell)
     cpp-options: -Dhls_floskell
   if flag(fourmolu)
     cpp-options: -Dhls_fourmolu
diff --git a/src/HlsPlugins.hs b/src/HlsPlugins.hs
--- a/src/HlsPlugins.hs
+++ b/src/HlsPlugins.hs
@@ -120,6 +120,11 @@
 import qualified Development.IDE.Plugin.CodeAction as Refactor
 #endif
 
+#if hls_semanticTokens
+import qualified Ide.Plugin.SemanticTokens         as SemanticTokens
+#endif
+
+
 data Log = forall a. (Pretty a) => Log PluginId a
 
 instance Pretty Log where
@@ -171,6 +176,9 @@
 #endif
 #if hls_callHierarchy
       CallHierarchy.descriptor "callHierarchy" :
+#endif
+#if hls_semanticTokens
+      let pId = "semanticTokens" in SemanticTokens.descriptor (pluginRecorder pId) pId:
 #endif
 #if hls_class
       let pId = "class" in Class.descriptor (pluginRecorder pId) pId:
diff --git a/src/Ide/Main.hs b/src/Ide/Main.hs
--- a/src/Ide/Main.hs
+++ b/src/Ide/Main.hs
@@ -14,7 +14,7 @@
 import qualified Data.Aeson.Encode.Pretty      as A
 import           Data.Coerce                   (coerce)
 import           Data.Default
-import           Data.List                     (sort)
+import           Data.List                     (sortOn)
 import           Data.Text                     (Text)
 import qualified Data.Text                     as T
 import           Data.Text.Lazy.Encoding       (decodeUtf8)
@@ -34,8 +34,9 @@
 import           Ide.Plugin.ConfigUtils        (pluginsToDefaultConfig,
                                                 pluginsToVSCodeExtensionSchema)
 import           Ide.Types                     (IdePlugins, PluginId (PluginId),
-                                                ipMap, pluginId)
+                                                describePlugin, ipMap, pluginId)
 import           Ide.Version
+import           Prettyprinter                 as PP
 import           System.Directory
 import qualified System.Directory.Extra        as IO
 import           System.FilePath
@@ -46,6 +47,7 @@
   | LogLspStart !GhcideArguments ![PluginId]
   | LogIDEMain IDEMain.Log
   | LogHieBios HieBios.Log
+  | LogSession Session.Log
   | LogOther T.Text
   deriving Show
 
@@ -61,6 +63,7 @@
           , "PluginIds:" <+> pretty (coerce @_ @[Text] pluginIds) ]
     LogIDEMain iDEMainLog -> pretty iDEMainLog
     LogHieBios hieBiosLog -> pretty hieBiosLog
+    LogSession sessionLog -> pretty sessionLog
     LogOther t -> pretty t
 
 defaultMain :: Recorder (WithPriority Log) -> Arguments -> IdePlugins IdeState -> IO ()
@@ -83,15 +86,17 @@
             putStrLn haskellLanguageServerNumericVersion
 
         ListPluginsMode -> do
-            let pluginNames = sort
-                    $ map ((\(PluginId t) -> T.unpack t) . pluginId)
+            let pluginSummary =
+                  PP.vsep
+                    $ map describePlugin
+                    $ sortOn pluginId
                     $ ipMap idePlugins
-            mapM_ putStrLn pluginNames
+            putStrLn $ show pluginSummary
 
         BiosMode PrintCradleType -> do
             dir <- IO.getCurrentDirectory
             hieYaml <- Session.findCradle def (dir </> "a")
-            cradle <- Session.loadCradle def hieYaml dir
+            cradle <- Session.loadCradle def (cmapWithPrio LogSession recorder) hieYaml dir
             print cradle
 
         Ghcide ghcideArgs -> do
@@ -107,8 +112,8 @@
           d <- getCurrentDirectory
           let initialFp = d </> "a"
           hieYaml <- Session.findCradle def initialFp
-          cradle <- Session.loadCradle def hieYaml d
-          (CradleSuccess libdir) <- HieBios.getRuntimeGhcLibDir (toCologActionWithPrio (cmapWithPrio LogHieBios recorder)) cradle
+          cradle <- Session.loadCradle def (cmapWithPrio LogSession recorder) hieYaml d
+          (CradleSuccess libdir) <- HieBios.getRuntimeGhcLibDir cradle
           putStr libdir
   where
     encodePrettySorted = A.encodePretty' A.defConfig
diff --git a/test/functional/Config.hs b/test/functional/Config.hs
--- a/test/functional/Config.hs
+++ b/test/functional/Config.hs
@@ -76,7 +76,7 @@
         testPluginId = "testplugin"
         -- A disabled-by-default plugin that creates diagnostics
         plugin = mkPluginTestDescriptor' @() pd testPluginId
-        pd plId = (defaultPluginDescriptor plId)
+        pd plId = (defaultPluginDescriptor plId "")
           {
             pluginConfigDescriptor = configDisabled
           , pluginRules = do
diff --git a/test/functional/ConfigSchema.hs b/test/functional/ConfigSchema.hs
new file mode 100644
--- /dev/null
+++ b/test/functional/ConfigSchema.hs
@@ -0,0 +1,49 @@
+module ConfigSchema where
+
+
+import qualified Data.ByteString.Lazy.Char8 as BS
+import           Data.Char                  (toLower)
+import           System.FilePath            ((</>))
+import           System.Process.Extra
+import           Test.Hls
+import           Test.Hls.Command
+
+-- | Integration test to capture changes to the generated default config and the vscode schema.
+--
+-- Changes to the vscode schema need to be communicated to vscode-haskell plugin maintainers,
+-- otherwise users can't make use of the new configurations.
+--
+-- In general, changes to the schema need to be done consciously when new plugin or features are added.
+-- To fix a failing of these tests, review the change. If it is expected, accept the change via:
+--
+-- @
+--   TASTY_PATTERN="generate schema" cabal test func-test --test-options=--accept
+-- @
+--
+-- As changes need to be applied for all GHC version specific configs, you either need to run this command for each
+-- GHC version that is affected by the config change, or manually add the change to all other golden config files.
+-- Likely, the easiest way is to run CI and apply the generated diffs manually.
+tests :: TestTree
+tests = testGroup "generate schema"
+  [ goldenGitDiff "vscode-extension-schema" (vscodeSchemaFp ghcVersion) $ do
+      stdout <- readProcess hlsExeCommand ["vscode-extension-schema"] ""
+      pure $ BS.pack stdout
+  , goldenGitDiff "generate-default-config" (defaultConfigFp ghcVersion) $ do
+      stdout <- readProcess hlsExeCommand ["generate-default-config"] ""
+      pure $ BS.pack stdout
+  ]
+
+vscodeSchemaFp :: GhcVersion -> FilePath
+vscodeSchemaFp ghcVer = "test" </> "testdata" </> "schema" </> prettyGhcVersion ghcVer </> vscodeSchemaJson
+
+defaultConfigFp :: GhcVersion -> FilePath
+defaultConfigFp ghcVer = "test" </> "testdata" </> "schema" </> prettyGhcVersion ghcVer </> generateDefaultConfigJson
+
+vscodeSchemaJson :: FilePath
+vscodeSchemaJson = "vscode-extension-schema.golden.json"
+
+generateDefaultConfigJson :: FilePath
+generateDefaultConfigJson = "default-config.golden.json"
+
+prettyGhcVersion :: GhcVersion -> String
+prettyGhcVersion ghcVer = map toLower (show ghcVer)
diff --git a/test/functional/Format.hs b/test/functional/Format.hs
--- a/test/functional/Format.hs
+++ b/test/functional/Format.hs
@@ -23,18 +23,18 @@
 
 providerTests :: TestTree
 providerTests = testGroup "lsp formatting provider"
-    [ testCase "respects none" $ runSessionWithConfig (formatConfig "none") hlsCommand fullCaps "test/testdata/format" $ do
+    [ testCase "respects none" $ runSessionWithConfig (formatConfig "none") hlsLspCommand fullCaps "test/testdata/format" $ do
         void configurationRequest
         doc <- openDoc "Format.hs" "haskell"
         resp <- request SMethod_TextDocumentFormatting $ DocumentFormattingParams Nothing doc (FormattingOptions 2 True Nothing Nothing Nothing)
         liftIO $ case resp ^. L.result of
           result@(Left (ResponseError reason message Nothing)) -> case reason of
             (InR ErrorCodes_MethodNotFound) -> pure () -- No formatter
-            (InR ErrorCodes_InvalidRequest) | "No plugin enabled for SMethod_TextDocumentFormatting" `T.isPrefixOf` message -> pure ()
+            (InR ErrorCodes_InvalidRequest) | "No plugin" `T.isPrefixOf` message -> pure ()
             _ -> assertFailure $ "strange response from formatting provider:" ++ show result
           result -> assertFailure $ "strange response from formatting provider:" ++ show result
 
-    , requiresOrmoluPlugin . requiresFloskellPlugin $ testCase "can change on the fly" $ runSessionWithConfig (formatConfig "none") hlsCommand fullCaps "test/testdata/format" $ do
+    , requiresOrmoluPlugin . requiresFloskellPlugin $ testCase "can change on the fly" $ runSessionWithConfig (formatConfig "none") hlsLspCommand fullCaps "test/testdata/format" $ do
         void configurationRequest
         formattedOrmolu <- liftIO $ T.readFile "test/testdata/format/Format.ormolu.formatted.hs"
         formattedFloskell <- liftIO $ T.readFile "test/testdata/format/Format.floskell.formatted.hs"
diff --git a/test/functional/FunctionalBadProject.hs b/test/functional/FunctionalBadProject.hs
--- a/test/functional/FunctionalBadProject.hs
+++ b/test/functional/FunctionalBadProject.hs
@@ -12,17 +12,17 @@
 tests :: TestTree
 tests = testGroup "behaviour on malformed projects"
     [ testCase "Missing module diagnostic" $ do
-        runSession hlsCommand fullCaps "test/testdata/missingModuleTest/missingModule/" $ do
+        runSession hlsLspCommand fullCaps "test/testdata/missingModuleTest/missingModule/" $ do
             doc <- openDoc "src/MyLib.hs" "haskell"
             [diag] <- waitForDiagnosticsFrom doc
             liftIO $ assertBool "missing module name" $ "MyLib" `T.isInfixOf` (diag ^. L.message)
             liftIO $ assertBool "module missing context" $ "may not be listed" `T.isInfixOf` (diag ^. L.message)
     , testCase "Missing module diagnostic - no matching prefix" $ do
-        runSession hlsCommand fullCaps "test/testdata/missingModuleTest/noPrefixMatch/" $ do
+        runSession hlsLspCommand fullCaps "test/testdata/missingModuleTest/noPrefixMatch/" $ do
             doc <- openDoc "app/Other.hs" "haskell"
             [diag] <- waitForDiagnosticsFrom doc
             liftIO $ assertBool "missing module name" $
                 "Other" `T.isInfixOf` (diag ^. L.message)
             liftIO $ assertBool  "hie-bios message" $
-                "Cabal {component = Just \"exe:testExe\"}" `T.isInfixOf` (diag ^. L.message)
+                "Cabal" `T.isInfixOf` (diag ^. L.message)
     ]
diff --git a/test/functional/HieBios.hs b/test/functional/HieBios.hs
--- a/test/functional/HieBios.hs
+++ b/test/functional/HieBios.hs
@@ -11,7 +11,7 @@
 tests :: TestTree
 tests = testGroup "hie-bios"
   [ testCase "loads main-is module" $ do
-    runSession hlsCommand fullCaps "test/testdata/hieBiosMainIs" $ do
+    runSession hlsLspCommand fullCaps "test/testdata/hieBiosMainIs" $ do
       _ <- openDoc "Main.hs" "haskell"
       (diag:_) <- waitForDiagnostics
       liftIO $ "Top-level binding with no type signature:" `T.isInfixOf` (diag ^. L.message)
diff --git a/test/functional/Main.hs b/test/functional/Main.hs
--- a/test/functional/Main.hs
+++ b/test/functional/Main.hs
@@ -1,6 +1,7 @@
 module Main where
 
 import           Config
+import           ConfigSchema
 import           Format
 import           FunctionalBadProject
 import           HieBios
@@ -10,6 +11,7 @@
 main :: IO ()
 main = defaultTestRunner $ testGroup "haskell-language-server"
     [ Config.tests
+    , ConfigSchema.tests
     , ignoreInEnv [HostOS Windows, GhcVer GHC90, GhcVer GHC92] "Tests gets stuck in ci" $ Format.tests
     , FunctionalBadProject.tests
     , HieBios.tests
diff --git a/test/functional/Progress.hs b/test/functional/Progress.hs
--- a/test/functional/Progress.hs
+++ b/test/functional/Progress.hs
@@ -28,12 +28,12 @@
     testGroup
         "window/workDoneProgress"
         [ testCase "sends indefinite progress notifications" $
-            runSession hlsCommand progressCaps "test/testdata/diagnostics" $ do
+            runSession hlsLspCommand progressCaps "test/testdata/diagnostics" $ do
                 let path = "Foo.hs"
                 _ <- openDoc path "haskell"
                 expectProgressMessages [pack ("Setting up diagnostics (for " ++ path ++ ")"), "Processing", "Indexing"] []
         , requiresEvalPlugin $ testCase "eval plugin sends progress reports" $
-            runSession hlsCommand progressCaps "plugins/hls-eval-plugin/test/testdata" $ do
+            runSession hlsLspCommand progressCaps "plugins/hls-eval-plugin/test/testdata" $ do
               doc <- openDoc "T1.hs" "haskell"
               lspId <- sendRequest SMethod_TextDocumentCodeLens (CodeLensParams Nothing Nothing doc)
 
@@ -57,7 +57,7 @@
                       expectProgressMessages ["Evaluating"] activeProgressTokens
                   _ -> error $ "Unexpected response result: " ++ show response
         , requiresOrmoluPlugin $ testCase "ormolu plugin sends progress notifications" $ do
-            runSessionWithConfig (def { ignoreConfigurationRequests = False }) hlsCommand progressCaps "test/testdata/format" $ do
+            runSessionWithConfig (def { ignoreConfigurationRequests = False }) hlsLspCommand progressCaps "test/testdata/format" $ do
                 void configurationRequest
                 setHlsConfig (formatLspConfig "ormolu")
                 doc <- openDoc "Format.hs" "haskell"
@@ -65,7 +65,7 @@
                 _ <- sendRequest SMethod_TextDocumentFormatting $ DocumentFormattingParams Nothing doc (FormattingOptions 2 True Nothing Nothing Nothing)
                 expectProgressMessages ["Formatting Format.hs"] []
         , requiresFourmoluPlugin $ testCase "fourmolu plugin sends progress notifications" $ do
-            runSessionWithConfig (def { ignoreConfigurationRequests = False }) hlsCommand progressCaps "test/testdata/format" $ do
+            runSessionWithConfig (def { ignoreConfigurationRequests = False }) hlsLspCommand progressCaps "test/testdata/format" $ do
                 void configurationRequest
                 setHlsConfig (formatLspConfig "fourmolu")
                 doc <- openDoc "Format.hs" "haskell"
diff --git a/test/utils/Test/Hls/Command.hs b/test/utils/Test/Hls/Command.hs
--- a/test/utils/Test/Hls/Command.hs
+++ b/test/utils/Test/Hls/Command.hs
@@ -1,5 +1,8 @@
 module Test.Hls.Command
-  ( hlsCommand
+  ( hlsExeCommand
+  , hlsLspCommand
+  , hlsWrapperLspCommand
+  , hlsWrapperExeCommand
   )
 where
 
@@ -12,8 +15,20 @@
 -- Both @stack test@ and @cabal new-test@ setup the environment so @hls@ is
 -- on PATH. Cabal seems to respond to @build-tool-depends@ specifically while
 -- stack just puts all project executables on PATH.
-hlsCommand :: String
-{-# NOINLINE hlsCommand #-}
-hlsCommand = unsafePerformIO $ do
+hlsExeCommand :: String
+{-# NOINLINE hlsExeCommand #-}
+hlsExeCommand = unsafePerformIO $ do
   testExe <- fromMaybe "haskell-language-server" <$> lookupEnv "HLS_TEST_EXE"
-  pure $ testExe ++ " --lsp -d -j4"
+  pure testExe
+
+hlsLspCommand :: String
+hlsLspCommand = hlsExeCommand ++ " --lsp -d -j4"
+
+hlsWrapperLspCommand :: String
+hlsWrapperLspCommand = hlsWrapperExeCommand ++ " --lsp -d -j4"
+
+hlsWrapperExeCommand :: String
+{-# NOINLINE hlsWrapperExeCommand #-}
+hlsWrapperExeCommand = unsafePerformIO $ do
+  testExe <- fromMaybe "haskell-language-server-wrapper" <$> lookupEnv "HLS_WRAPPER_TEST_EXE"
+  pure testExe
