March 20, 2026

Using Language Servers with Direnv and Nix in Zed

Zed
Ruby
Nix
3 min read

Language servers (implemented via LSPs) need access to the correct respective language binaries and libraries in order to do their jobs.

Nix flakes provide our target environment for developers

When your team uses Nix flakes for package management, including local dev shells, like we do, you might run into the situation we did with language servers. Namely, that by default they use system-included binaries and libraries for things like ruby or python3.

That won’t do in a world controlled by Nix, because you have to get Nix to provide the correct binaries and environment by loading the devShell for your project, with all of its dependencies. We do this with the wonderful Direnv tool: every time a developer changes (cd) into an OMC project directory, there is a .envrc file that tells Direnv to use flake (among other things).

When language servers run in an editor, they also need Nix

Okay, so if a developer is running a terminal on their local project, direnv being loaded into their shell config handles the Nix dev shell setup and correctly resolves dependencies. But if you are trying to run a language server, it is running inside of an editor or IDE. Chances are that your editor knows nothing about Direnv or Nix.

At OMC, we use Zed across the team, mostly because it’s incredibly fast, has great support for editing code across many languages, and has good agent support (we use Claude Code).

So we just had to quickly teach Zed how to use direnv when starting up the language servers we care about. In this case, we’re looking at one of our Ruby projects, so the language servers are from the Ruby world.

The Zed project settings file

We all share the following config inside of the project repo:

// .zed/settings.json
{
  "languages": {
    "Ruby": {
      // Use solargraph and rubocop; disable ruby-lsp
      "language_servers": ["solargraph", "rubocop", "!ruby-lsp", "..."]
    }
  },
  "lsp": {
    // Wrap with direnv so language servers see the Nix flake environment (Ruby 3.3.x + project gems).
    // Without this, Zed uses the system Ruby 3.4.x and bundler finds stub specs compiled
    // for the wrong Ruby, causing "missing extensions" warnings and wasm trap errors.
    "solargraph": {
      "binary": {
        // Uses bin/solargraph-lsp to fix a solargraph-rails plugin loading issue.
        "path": "direnv",
        "arguments": ["exec", ".", "solargraph-lsp"]
      }
    },
    "rubocop": {
      "binary": {
        "path": "direnv",
        "arguments": ["exec", ".", "rubocop", "--lsp"]
      }
    }
  }
}

None of this is ground-breaking, but it shows a clean pattern for replacing two language server binaries with explicit calls to direnv and using its exec subcommand to set the project context (. is the project’s top-level directory) and then invoke each language server.

The only wrinkle I hit in this one was that solargraph-rails, for some reason, relies on the Object#present? method from ActiveSupport. So Claude helped write a small wrapper script in our ./bin directory that just wraps the language server with a quick require:

$ cat bin/solargraph-lsp
#!/usr/bin/env sh
# Wrapper for solargraph stdio that pre-loads active_support/core_ext/object/blank
# so the solargraph-rails plugin can call Object#present? without crashing.
exec env RUBYOPT="-ractive_support/core_ext/object/blank" solargraph stdio "$@"

And now our language servers load correctly from Nix via Direnv in Zed!

Ready to take a closer look at Bonsai?

Bonsai manages your search clusters and helps you achieve better search results for your users and your business. Find out if Bonsai is a good fit for you in just 15 minutes.

Learn how a managed service works and why it’s valuable to dev teams

You won’t be pressured or used in any manipulative sales tactics

We’ll get a deep understanding of your current tech stack and needs

Get the information you need to decide whether to go with Bonsai

Calming Bonsai waves