Haskell World logo Haskell WorldWrite code, build worlds
Developer Tools

Haskell Language Server Troubleshooting for Real Projects

You open a module, the editor shows a spinner for forty seconds, and then every import is underlined in red even though cabal build finished cleanly a minute ago.

Code editor showing Haskell Language Server diagnostics next to a hie.yaml file

You open a module, the editor shows a spinner for forty seconds, and then every import is underlined in red even though cabal build finished cleanly a minute ago. That is the most common Haskell Language Server complaint I hear from people who join a team codebase, and in my experience nine times out of ten the server itself is fine. Something around it is mismatched.

This guide is the checklist I run before I blame HLS. It covers GHC version pairing, the cradle that tells the server how to load your project, memory on large repositories, and the plugins that quietly eat CPU.

Why Haskell Language Server Breaks More Often Than Other Language Servers

Most language servers parse source files and stop there. HLS does something heavier: it embeds the GHC API and typechecks your code with the exact compiler your project uses. That gives you accurate types on hover and real error messages, but it also means the server binary is tied to one specific GHC version. A server built for GHC 9.6.6 cannot load a project compiled with 9.8.2, and it will not always say so clearly.

The second source of trouble is that HLS has to reproduce your build. It asks Cabal or Stack for the flags, package databases and source directories of every component. If that question returns the wrong answer, you get phantom errors that disappear the moment you run a normal build.

Match the Server to Your Compiler First

Start here. The wrapper binary, haskell-language-server-wrapper, reads your project, works out which GHC it needs, and launches the matching server binary. If that binary is missing, the wrapper may fall back to whatever it finds on your PATH.

  1. Run ghc with the numeric version flag inside the project folder and note the exact version.
  2. Open ghcup tui and check that a server build supporting that GHC is installed. Supported versions are marked in the list.
  3. If your GHC has no prebuilt server, either move the project to a supported minor release or let ghcup compile HLS for it, which takes 20 to 40 minutes on a laptop.
  4. Restart the editor fully. Reloading the window is not always enough, because the old server process can survive.

I covered the toolchain side in more depth in our guide to setting up a Haskell environment, including why ghcup is the least painful way to keep GHC, Cabal and HLS in step.

Read the Server Log Before Changing Anything

Every editor exposes the HLS output somewhere. In VS Code it is the Output panel with the Haskell channel selected. In Neovim it depends on your LSP client, but the log file path is printed at startup. Look for three lines in particular:

  • The GHC version the server reports it was built with.
  • The cradle type it chose, which tells you whether it is using Cabal, Stack or a direct GHC invocation.
  • The first error after the line that mentions loading the cradle.

That first error is the real one. A missing package database, a failed preprocessor such as hpack or alex, or a Template Haskell splice that needs a C library will all show up there before the red underlines appear in your files.

Fix the Cradle With a Small hie.yaml

HLS uses a library called implicit hie to guess how your project is built. It guesses well for a single package. It guesses badly for repositories with several packages, test suites that share modules with the library, or a mix of Cabal and Stack files left over from a migration.

When the guess is wrong, write a hie.yaml at the repository root. For a Cabal project with a library, an executable and a test suite, it needs only a few lines: a top level cradle key, a cabal key, and a list of components, each pairing a path such as src or test with a component name such as lib:myapp or test:myapp-tests. Being explicit stops HLS from loading a test module as if it belonged to the library, which is what produces those confusing errors about hidden packages like hspec.

When a monorepo needs more than one cradle

If one folder holds a Stack project and another holds plain Cabal packages, use a multi cradle and map each directory to its own tool. It looks clumsy, but it beats letting the server try every option on every file open.

The Advice to Disable Plugins Is Usually Wrong

Forum threads about slow HLS almost always end with someone saying to turn off every plugin. I disagree. Most plugins cost very little, and disabling all of them throws away the reason you installed the server: inline type signatures, import suggestions and quick fixes for missing pattern cases.

A small number of plugins do carry a real cost on large modules. Profile before you cut anything.

PluginTypical costWhat I do
hlintModerate on files over 1,000 linesKeep it, run on save only
Formatter (ormolu or fourmolu)Low, only when invokedKeep it
EvalHigh if comments contain many examplesDisable in big repositories
RenameHigh across many modulesUse it, but expect a pause

The plugin that most often surprises people is Eval. It re-runs example expressions in comments, and one heavily documented module can keep a core busy for minutes.

Keep Memory Under Control on Large Codebases

On a project with around 400 modules I regularly see HLS reach 6 to 8 GB of resident memory after a long session. That is not a leak in the usual sense. The server keeps typechecked modules and their interface files in memory so that hover and go to definition stay instant.

Three changes help without hurting responsiveness much:

  • Set the server to check only open files rather than the whole project. You lose project wide diagnostics, but your build or CI still catches those.
  • Pass runtime options to cap the heap. A limit around 4 GB forces more frequent collection and keeps the machine usable. If you want to understand what the collector is doing, the RTS statistics report applies to HLS just as it does to your own programs.
  • Restart the server once or twice a day. Every editor integration has a command for it.

FAQ

Should I use HLS or ghcid for day to day work?

Both, if you can. HLS gives you hover, completion and quick fixes inside the editor. Ghcid runs a GHCi session in a terminal and reports errors faster on very large projects. Many developers keep ghcid open as the source of truth and treat HLS as a navigation tool, a habit that pairs well with a GHCi centred workflow.

Why does HLS work for the library but not the test suite?

Almost always a cradle issue. The test component has dependencies such as hspec or tasty that the library does not, and if HLS loads test files with the library component it cannot find them. An explicit hie.yaml entry for the test directory fixes it.

Is it safe to delete the HLS cache?

Yes. The cache lives under your user cache directory in a folder named ghcide. Deleting it forces a full reindex on the next start, which can take several minutes on a big project, but it clears stale interface files that cause errors nothing else explains.

A Short Routine That Saves Hours

When Haskell Language Server misbehaves, work through the same order every time: confirm the GHC version, read the first error in the log, write an explicit hie.yaml, then look at memory and individual plugins. Most problems end at step two or three. Write your working hie.yaml into the repository so the next person who joins never sees that forty second spinner, and browse the rest of our developer tools articles when you are ready to tune the rest of your setup.

FB
Finnian Burke

Finnian Burke spent eight years building distributed systems before leaving to write about the programming patterns that actually hold up under pressure. He has worked with Haskell, Rust, and Go in production, and writes mostly about the parts of functional programming that turn out to be useful regardless of the language you use day to day.

More posts by Finnian

More in Developer Tools

A GHCi session with type queries and a reloaded module Developer Tools

Using GHCi as Your Daily Haskell Workflow

Many Haskell beginners treat GHCi as a calculator they poke at occasionally, then do their real work by compiling full programs...

Sonja ReiterOct 15, 20265 min read
01doorganico 084goad 3hot8 4465456 after5internprogram ahandfulofblessings ahmadpc ahomcybersecurity allodialresources annodex antiquesandperiodcollectibles aotc asianworldkashmir beedlectrics bloggertemplatefree bluefeatheragency bodyweightsolution campaign08blog carfixadvisor catstreetcollective cbstvsports cnfanspurchasing committeeof500yearsofdignityandresistance covertopsdjs cs-kaiyuangame cuestacomunicaciontotal cxoutlet dancebetgame deskgearhub destinyvillastay detectmagazine di33 diamondsteelbuildings donmo dyanadropship emarsysfinance emimattl escapechan eskitarih evautoplay everythingweddings-orlando farmzone feedviewerapp foxstudiosscratch gamevui123 gleasongrade greetingsday hacklabasu iw-kaiyun jagoanproperti jilidskripsi joltbusinessfinance juegodepokergratis juegospucca kaeru-festival latestmehndidesigns lestanzediottaviano litemagazines localcommunityhero lustrsazan mariashebets menswheels newwebstechs1 nikkit ootheday oxigenvpn plurfitness prefetial probudgetcarpetcleaning psychicmrsmartin pyaten qvantor rejoignezlogis rexzbot riselayouts ruchin rwxqsgd5 sakaltime shibiaowang shop-bot skrtips strcoding tetapatrium topselller uneplanteartificielle vipertechcr webtech1223411 wo-kygaming yamaha-mekar yeknava yoybuysheets zenunwa yamaha-mekar jilidskripsi ankertoto ankertoto ankertoto ankertoto