Skip to content

Add nvim server hopping with local and remote session support#309

Merged
stanfish06 merged 2 commits into
masterfrom
claude/neovim-server-improvements-cxbbjm
Jul 18, 2026
Merged

Add nvim server hopping with local and remote session support#309
stanfish06 merged 2 commits into
masterfrom
claude/neovim-server-improvements-cxbbjm

Conversation

@stanfish06

Copy link
Copy Markdown
Owner

Summary

This PR adds a comprehensive server hopping system for Neovim that enables seamless navigation between local and remote nvim instances, replacing manual tmux/sesh workflows. The implementation includes fuzzy picking over live servers and zoxide directories, SSH remote spawning with socket forwarding, and automatic cleanup of temporary sessions.

Key Changes

  • Server Management: Enhanced server listing and inspection to track metadata (host, working directory, labels) and detect dead/orphaned sessions
  • Local Session Spawning: Added spawn_local_server() to create headless nvim instances at specified directories with automatic zoxide integration
  • Remote Session Support: Implemented remote_session() and forward_remote() to spawn nvim on SSH hosts and tunnel connections through local sockets with automatic cleanup
  • Hop Picker: New open_hop_picker() function providing fuzzy selection across live servers and zoxide directories (local or per-host), with ctrl-x to kill servers
  • SSH Integration: Configured SSH with ControlMaster connection pooling for efficient repeated connections, BatchMode for key/agent auth, and host completion from ~/.ssh/config
  • Disposable Instance Handling: Added is_disposable() to identify empty launch dummies that auto-quit when hopping away, preventing orphaned servers
  • New Commands:
    • :NvimHop [host] - Fuzzy picker for servers/directories (local or on SSH host)
    • :NvimRemote {host} - Spawn and connect to a server on an SSH host
    • :ShowNvimServers - Enhanced with metadata display
  • New Keymaps:
    • <leader>ss - Local hop picker
    • <leader>sr - SSH host selector then hop picker
  • Auto-Launch Behavior: Plain nvim invocation now offers hop picker if other servers exist (disable with vim.g.nvim_server_autopick = false)

Implementation Details

  • Socket metadata stored in .meta files (JSON) to track SSH forwards and remote hosts
  • Unique socket naming scheme: nvim.{tag}.{pid}.{counter} in $XDG_RUNTIME_DIR/hop/ or equivalent
  • SSH forwards use -N tunneling with StreamLocalBindUnlink for clean socket management
  • Remote spawning via shell script executed over SSH, with exit codes indicating specific failure modes
  • Automatic cleanup of dead forward processes and orphaned socket files during server listing
  • Integration with fzf-lua when available, fallback to vim.ui.select

https://claude.ai/code/session_017J2ZenpCfVCDmNN1M3mFSL

claude and others added 2 commits July 18, 2026 02:31
- connect_to() lets an empty launch dummy quit itself once its UI hops
  away, so :connect no longer leaves orphan servers behind; plain
  'nvim' launches pop the hop picker when other servers exist
  (vim.g.nvim_server_autopick = false to disable)
- :NvimHop (<leader>ss): fzf-lua picker over live servers (labeled by
  cwd) merged with zoxide dirs; <CR> connects or spawns a detached
  headless server at the dir, ctrl-x kills a server
- :NvimRemote {host} / :NvimHop {host} (<leader>sr): spawn a detached
  headless nvim on an ssh host (landing dir or remote zoxide dir) and
  reach it through an ssh -L forwarded unix socket, so connect, rename
  and kill work on remote servers exactly like local ones; ssh uses a
  shared ControlMaster as the jump point and dead forwards are reaped
  on listing

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_017J2ZenpCfVCDmNN1M3mFSL

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 51cc05a0c5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread lua/config/server.lua
vim.cmd("qa!")
end
end, 1500)
local ok, err = pcall(vim.cmd, "connect " .. vim.fn.fnameescape(path))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Forward remote targets before connecting

When this function is running inside a server that was reached through NvimRemote, path can be a socket path created or discovered on that remote host (for example, selecting a zoxide dir spawns a server there and then calls this function). :help :connect says the UI attaches to the server at {address}, so the local TUI tries to open that remote filesystem path locally; subsequent hops from a remote session therefore fail unless the target socket is forwarded back to the UI host first.

Useful? React with 👍 / 👎.

Comment thread lua/config/server.lua
Comment on lines +75 to +76
if meta and meta.fwd_pid then
pcall(vim.uv.kill, meta.fwd_pid, "sigterm")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate the forward PID before killing it

When a forwarded Unix socket and .meta file survive after the ssh tunnel exits, the next server scan takes this path and sends SIGTERM to the recorded PID before removing the files. Since PIDs can be reused, this can terminate an unrelated process long after the original forward died; store enough identity to verify the process is still the expected ssh forward, or avoid killing by stale PID.

Useful? React with 👍 / 👎.

Comment thread lua/config/server.lua
Comment on lines +512 to +514
local localsock = forward_remote(host, remote_sock)
if localsock then
connect_to(localsock)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clean up remote servers after forward failures

If the remote nvim starts successfully but forward_remote() returns nil (for example because Unix-socket forwarding is disabled on the host or the local socket never appears), this callback just stops and leaves the headless remote server running at remote_sock with no local metadata or picker entry to reach it. Add a best-effort remote qa!/cleanup path when forwarding fails so failed connection attempts do not orphan remote sessions.

Useful? React with 👍 / 👎.

Comment thread lua/config/server.lua

local function remote_session(host, dir)
vim.notify("Spawning nvim on " .. host .. (dir and (" at " .. dir) or "") .. " ...")
local cmd = vim.list_extend(ssh_cmd(), { "-T", host, remote_spawn_script(dir) })

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Run the remote bootstrap with a POSIX shell

On SSH hosts whose login shell is not POSIX-compatible, such as fish or csh, this passes the generated script directly as the remote command, so that shell parses the ||, [ -S ... ], $((...)), and assignment syntax used by remote_spawn_script(). Those hosts fail before spawning nvim even when ssh and nvim are configured correctly; invoke sh -lc or otherwise force a POSIX shell for the bootstrap script.

Useful? React with 👍 / 👎.

@stanfish06
stanfish06 merged commit 879d1a1 into master Jul 18, 2026
6 checks passed
@stanfish06
stanfish06 deleted the claude/neovim-server-improvements-cxbbjm branch July 18, 2026 02:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants