one server. no resistance.
A persistent LSP process manager daemon for Neovim. Fixes memory bloat, stuck diagnostics, monorepo server duplication, and session degradation — the recurring pain points in Neovim's LSP lifecycle.
Neovim starts a new LSP server per session, leaks memory, leaves stuck diagnostics on detach, and spawns duplicate servers in monorepos. ohm solves it at the daemon layer.
Neovim instances (any number)
↕ stdio (LSP JSON-RPC via ohm --client bridge)
ohm daemon — fan-out multiplexer, request ID rewriting
↕ stdio (LSP JSON-RPC)
LSP servers (gopls, rust-analyzer, tsserver, ...)
- Shared servers — one LSP process per
{root_dir, language}pair, shared across all Neovim sessions. - Fan-out multiplexer — rewrites request IDs per client, routes responses back to the correct session.
- Ref counting — tracks attached buffers. Server stays alive while any buffer is open.
- Grace period — when refs hit 0, waits 10s before killing. Reopen a file within the window to cancel.
- Diagnostic fence — sends
textDocument/didClosebefore detach to prevent stuck diagnostics. - Respawn — crashed servers are automatically restarted without losing the proxy socket.
- Watchdog — kills servers exceeding 1500MB RSS or frozen for 5+ minutes.
- Shutdown interception — intercepts client
shutdown/exitso individual session closes don't kill the shared server.
- Neovim 0.9+
- Go 1.21+ (only if building from source)
Step 1 — download the binary for your platform:
# Linux x86_64
curl -L https://github.com/ryan-WORK/ohm/releases/latest/download/ohm-linux-amd64 -o ~/.local/bin/ohm
# Linux arm64
curl -L https://github.com/ryan-WORK/ohm/releases/latest/download/ohm-linux-arm64 -o ~/.local/bin/ohm
# macOS Apple Silicon
curl -L https://github.com/ryan-WORK/ohm/releases/latest/download/ohm-darwin-arm64 -o ~/.local/bin/ohm
# macOS Intel
curl -L https://github.com/ryan-WORK/ohm/releases/latest/download/ohm-darwin-amd64 -o ~/.local/bin/ohm
chmod +x ~/.local/bin/ohmMake sure ~/.local/bin is on your PATH. Verify with ohm --help.
Step 2 — add the plugin (no build hook needed):
{
"ryan-WORK/ohm",
config = function()
require("ohm").setup()
end,
}Requires Go 1.21+.
{
"ryan-WORK/ohm",
build = "./build.sh",
config = function()
require("ohm").setup()
end,
}build.sh compiles the binary into bin/ohm inside the plugin directory on install and update.
mason registry submission in progress — project needs more stars.
# download
curl -L https://github.com/ryan-WORK/ohm/releases/latest/download/ohm-linux-amd64 -o ohm
chmod +x ohm
# verify checksum (replace hash with value from checksums.txt on the release page)
echo "<sha256> ohm" | sha256sum -cPass the path explicitly if not on PATH:
require("ohm").setup({ binary = "/path/to/ohm" })require("ohm").setup({
-- Path to ohm binary. Auto-detected from bin/ohm in plugin dir or PATH.
binary = nil,
-- Unix socket path for the control channel.
socket = vim.fn.stdpath("data") .. "/ohm.sock",
-- Enable verbose daemon logging (useful for debugging LSP issues).
debug = false,
})| Command | Description |
|---|---|
:OhmStatus |
Show active servers: PID, language, memory, refs, last response |
:OhmRestart |
Stop and restart the daemon |
# build
go build -o bin/ohm .
# run tests
go test ./...
# run daemon (silent by default — only warns/errors surface)
mkdir -p tmp && go run . tmp/ohm.sock
# run daemon with verbose logging
mkdir -p tmp && go run . --debug tmp/ohm.sockMIT