Ruby-lsp keeps crashing in your monorepo? Here's exactly how to fix it in Zed

Ruby-lsp crashes in monorepos because Zed launches it from the wrong directory. Your Gemfile is one level down, bundle can't find it, and the server loops restarting. Same root cause breaks your test runner tasks.
Layout: Rails API in server/, React frontend in client/. No Gemfile at the root.
monorepo/
├── client/
├── server/ # Gemfile + spec/ live here
│ ├── Gemfile
│ └── spec/
└── .git/
The gems you need
Zed's Ruby extension expects a language server from your project gems. Add these to server/Gemfile's :development group:
gem 'ruby-lsp', require: false
gem 'ruby-lsp-rails', '~> 0.4'
gem 'ruby-lsp-rspec', require: false
| Gem | What it adds |
|---|---|
ruby-lsp |
Go-to-definition, hover, completion, diagnostics |
ruby-lsp-rails |
Schema columns, routes, associations |
ruby-lsp-rspec |
Test discovery, "run test" code lens |
Why bundle exec fails from the worktree root
Zed runs language servers from the worktree root. When you open monorepo/:
cd /path/to/monorepo
bundle exec ruby-lsp # no Gemfile here
bundle walks up looking for a Gemfile, finds nothing, and exits. Zed restarts the server, it dies again, and you see Server reset the connection on repeat.
This isn't a ruby-lsp bug. It's a working-directory mismatch. The tool assumes a single-project layout, but the Ruby project is in a subdirectory.
Ruby-lsp logs are silent when it crashes this way. The reset notification is your only clue. The first time I hit this I spent an hour debugging my Ruby version before I thought to check where bundle was running.
The mise problem
Version managers (mise, rbenv, asdf) activate via shell config. Zed may not source your shell, so ruby/bundle aren't on PATH for the language server process. (Mise docs on shims explain why.)
Use the version manager's shims by absolute path. They resolve the correct Ruby per-directory without shell activation:
$ ls ~/.local/share/mise/shims/
bundle ruby ...
Three ways to point ruby-lsp at your Gemfile
1. Open the subproject directly
zed ~/dev/monorepo/server
Worktree root is now server/. Gemfile is right there. ruby-lsp starts cleanly.
Trade-off: Lose the single-window view of the whole repo.
2. Add server/ as a second worktree
Cmd/Ctrl-Shift-P → "Add Folder to Project" → pick server/.
Zed runs language servers per worktree. The server worktree gets its own ruby-lsp from a directory with a Gemfile. Both sides visible in one window.
Trade-off: Per-machine UI action. Can't commit and share.
3. A wrapper script (committable)
This lives in the repo and works for everyone.
.zed/ruby-lsp.sh:
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")/../server"
exec "$HOME/.local/share/mise/shims/bundle" exec ruby-lsp "$@"
cd "$(dirname "$0")/../server" resolves relative to the script's location, not the caller's cwd. exec with the mise shim by absolute path sidesteps PATH gaps. (rbenv? ~/.rbenv/shims/bundle. asdf? ~/.asdf/shims/bundle.)
Make it executable, then route the binary via .zed/settings.json (Zed's LSP binary config):
{
"lsp": {
"ruby-lsp": {
"binary": { "path": ".zed/ruby-lsp.sh" }
}
}
}
Smoke test:
$ timeout 5 .zed/ruby-lsp.sh < /dev/null
# (no output, hangs; success)
Could not locate Gemfile → wrong cd path. command not found → wrong shim path. Restart: "editor: restart language server".
Same problem, same fix for tasks
Zed tasks launch from the worktree root too. A naive RSpec task hits the same wall:
⏵ Task `test server/spec/.../organizations_spec.rb` finished with exit code: 10
Broken server/.zed/tasks.json:
[{
"label": "test $ZED_RELATIVE_FILE",
"command": "bundle",
"args": ["exec", "rspec", "\"$ZED_RELATIVE_FILE\""],
"cwd": "$ZED_WORKTREE_ROOT",
"tags": ["ruby-test"]
}]
Two bugs:
cwdis the repo root and there's no Gemfile there.$ZED_RELATIVE_FILEisserver/spec/.... Fix cwd toserver/and that path becomesserver/server/spec/..., which doesn't exist.
Fix:
[{
"label": "test $ZED_RELATIVE_FILE",
"command": "bundle",
"args": ["exec", "rspec", "\"$ZED_FILE\""],
"cwd": "$ZED_WORKTREE_ROOT/server",
"tags": ["ruby-test"]
}]
| Field | Before | After | Why |
|---|---|---|---|
cwd |
worktree root | .../server |
So bundle finds the Gemfile |
| file arg | $ZED_RELATIVE_FILE |
$ZED_FILE |
Absolute path, immune to cwd change |
Run a single example with $ZED_ROW:
[{
"label": "test (line) $ZED_RELATIVE_FILE:$ZED_ROW",
"command": "bundle",
"args": ["exec", "rspec", "\"$ZED_FILE:$ZED_ROW\""],
"cwd": "$ZED_WORKTREE_ROOT/server",
"tags": ["ruby-test"]
}]
Verify by hand:
$ cd ~/dev/monorepo/server
$ ~/.local/share/mise/shims/bundle exec rspec \
"/home/me/dev/monorepo/server/spec/requests/api/super_admin/organizations_spec.rb" \
--dry-run
--dry-run loads everything and runs nothing. Fast path confirmation.
What to commit
monorepo/
├── .zed/
│ ├── ruby-lsp.sh
│ └── settings.json
├── client/
└── server/
├── .zed/
│ └── tasks.json
├── Gemfile
└── spec/
No more server resets, no more failing tasks. Commit .zed/ and the next person who clones gets a working setup on open.



