# 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.

```plaintext
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:

```ruby
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/`:

```bash
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](https://mise.jdx.dev/dev-tools/shims.html) explain why.)

Use the version manager's **shims** by absolute path. They resolve the correct Ruby per-directory without shell activation:

```bash
$ ls ~/.local/share/mise/shims/
bundle  ruby  ...
```

## Three ways to point ruby-lsp at your Gemfile

### 1\. Open the subproject directly

```bash
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`:

```bash
#!/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](https://zed.dev/docs/configuring-zed#lsp)):

```json
{
  "lsp": {
    "ruby-lsp": {
      "binary": { "path": ".zed/ruby-lsp.sh" }
    }
  }
}
```

Smoke test:

```bash
$ 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:

```plaintext
⏵ Task `test server/spec/.../organizations_spec.rb` finished with exit code: 10
```

Broken `server/.zed/tasks.json`:

```json
[{
  "label": "test $ZED_RELATIVE_FILE",
  "command": "bundle",
  "args": ["exec", "rspec", "\"$ZED_RELATIVE_FILE\""],
  "cwd": "$ZED_WORKTREE_ROOT",
  "tags": ["ruby-test"]
}]
```

Two bugs:

1.  `cwd` is the repo root and there's no Gemfile there.
    
2.  `$ZED_RELATIVE_FILE` is `server/spec/...`. Fix cwd to `server/` and that path becomes `server/server/spec/...`, which doesn't exist.
    

Fix:

```json
[{
  "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`:

```json
[{
  "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:

```bash
$ 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

```plaintext
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.
