How to Fix Stack Downloading Separate GHC Versions Instead of Using GHCup

Summary

A developer inadvertently deleted their global Haskell Stack configuration directory (~/.stack) while troubleshooting an unrelated issue. This action destroyed the integration layer between stack and ghcup. While stack can still function, it lost the ability to delegate GHC (Glasgow Haskell Compiler) version management to ghcup. This results in stack attempting to download its own isolated versions of GHC, bypassing the centralized management provided by ghcup and breaking the seamless link with Haskell Language Server (HLS) binaries.

Root Cause

The failure stems from the loss of the config.yaml file located within the ~/.stack directory. This file contains the specific configuration directives required for the integration. Specifically:

  • Configuration Erasure: The deletion of ~/.stack removed the instructions that tell stack how to find and use the GHC toolchains managed by ghcup.
  • Loss of Path Mapping: Without the configuration, stack reverts to its default behavior, which is to manage its own internal, sandboxed GHC installations.
  • Integration Decoupling: The mechanism that allows stack to “borrow” GHC versions from the ghcup installation path was lost.

Why This Happens in Real Systems

In complex development environments, stateful configuration directories are often treated as “disposable” by engineers trying to perform a “clean slate” reset. This happens because:

  • Undocumented Dependencies: Users often forget that a tool (like stack) can be configured to depend on the state or settings of another tool (ghcup).
  • Implicit Coupling: The integration isn’t a hard system link but a soft configuration link. Deleting the config doesn’t break the binaries, but it breaks the orchestration logic between them.
  • Debugging Side-Effects: When troubleshooting a deep issue, engineers often perform “scorched earth” tactics—deleting home directory dotfiles—without realizing the downstream impact on tool interop.

Real-World Impact

The impact of losing this integration is primarily related to developer velocity and system consistency:

  • Disk Bloat: stack will begin downloading multiple copies of GHC into its own internal storage, duplicating the space already used by ghcup.
  • Toolchain Mismatch: The Haskell Language Server (HLS) may struggle to find the correct GHC version if stack is using a different binary than the one ghcup has prepared for the IDE.
  • Build Inconsistency: Developers might end up with different compiler versions across different projects, leading to the “it works on my machine” problem when CI/CD uses a different managed toolchain.

Example or Code

To recover the integration, you must manually re-inject the ghcup path configuration into a new config.yaml file.

# ~/.stack/config.yaml
install-ghc: true
compiler: ghc-9.4.5
system-ghc: true

Note: The system-ghc: true flag is the critical component that instructs stack to look for the compiler in the system PATH (where ghcup places it) rather than downloading its own.

How Senior Engineers Fix It

A senior engineer approaches this by treating the configuration as reproducible state rather than a manual chore.

  • Re-establishing the Link: Instead of just reinstalling tools, they explicitly configure the system-ghc flag to ensure stack honors the existing environment.
  • Environment Auditing: They verify the PATH to ensure ghcup binaries are prioritized, allowing stack to see the managed GHCs immediately.
  • Configuration as Code: To prevent this in the future, a senior engineer would store their dotfiles (like .stack/config.yaml) in a Git repository (dotfiles repo), making recovery as simple as a git clone.

Why Juniors Miss It

Junior engineers often miss the systemic nature of the problem:

  • Focus on Binaries, Not Config: They might try to fix the issue by reinstalling stack or ghcup, not realizing the binaries are fine—it is the metadata that is missing.
  • Symptom-Based Troubleshooting: They see “GHC not found” and assume the compiler is gone, rather than realizing the pointer to the compiler was deleted.
  • Lack of State Awareness: They treat the ~/.stack directory as a cache (disposable) rather than a configuration store (persistent).

Leave a Comment