Skip to content

Remote Sessions

Navi can aggregate Claude Code sessions from multiple remote machines via SSH into a single unified dashboard. Remote sessions have full feature parity with local sessions — you can view git info, preview output, attach, kill, rename, and dismiss them.

  1. Create the configuration file at ~/.config/navi/remotes.yaml:
remotes:
- name: dev-server
host: dev.example.com
user: sean
key: ~/.ssh/id_rsa
- name: staging
host: staging.example.com
user: deploy
key: ~/.ssh/deploy_key
jump_host: bastion.example.com
  1. Start navi — remote sessions appear automatically alongside local sessions, labeled with the remote name:
⚙️ my-local-session 2m ago
~/projects/app
⚙️ api [dev-server] 5m ago
~/work/api
"Running tests..."
✅ deploy [staging] 10m ago
~/app
"Deployment complete"

Remote machines are configured via a YAML file at ~/.config/navi/remotes.yaml. Navi reads this file on startup. If the file doesn’t exist, remote support is simply disabled — no error.

remotes:
- name: dev-server # Display name (required)
host: dev.example.com # SSH hostname or IP (required)
user: sean # SSH username (required)
key: ~/.ssh/id_rsa # Path to SSH private key (required)
sessions_dir: ~/.claude-sessions # Remote sessions directory (optional)
jump_host: bastion.example.com # Jump/bastion host (optional)
FieldTypeRequiredDefaultDescription
namestringYesDisplay name shown in the TUI next to remote sessions
hoststringYesSSH hostname or IP address
userstringYesSSH username
keystringYesPath to SSH private key file (supports ~ expansion)
sessions_dirstringNo~/.claude-sessionsPath to the sessions directory on the remote machine
jump_hoststringNoJump/bastion host for reaching the target (same user and key are used)

All four required fields (name, host, user, key) must be present or navi will report a validation error on startup.

Press Enter on a remote session. Navi runs:

Terminal window
ssh -i <key> -t user@host tmux attach-session -t <session>

If a jump host is configured, -J user@jumphost is added. When you detach from tmux, control returns to navi.

Press p to toggle the preview pane. For remote sessions, navi captures output via SSH:

Terminal window
tmux capture-pane -t <session> -p -S -<lines>

ANSI escape sequences are stripped from the output before display. The preview works identically to local preview — same layouts, scrolling, and keybindings.

Press G on a remote session. Navi runs a bundled git command over SSH in the session’s working directory:

Terminal window
cd <cwd> && \
echo "BRANCH:$(git rev-parse --abbrev-ref HEAD 2>/dev/null)" && \
echo "DIRTY:$(git status --porcelain 2>/dev/null | head -1)" && \
echo "REMOTE:$(git remote get-url origin 2>/dev/null)" && \
echo "LASTCOMMIT:$(git log -1 --format='%h %s' 2>/dev/null)" && \
echo "AHEADBEHIND:$(git rev-list --left-right --count @{u}...HEAD 2>/dev/null)"

The output is parsed into the same git.Info structure used for local sessions. GitHub PR detection uses the local gh CLI with the remote URL — gh does not need to be installed on the remote machine.

Press x on a remote session. After confirmation, navi runs:

Terminal window
tmux kill-session -t <session> ; rm -f <sessions_dir>/<session>.json

The ; ensures the status file is cleaned up even if the tmux session was already gone.

Press R on a remote session. After entering the new name, navi runs a single compound command that:

  1. Renames the tmux session
  2. Updates the tmux_session field inside the JSON status file
  3. Renames the JSON file to match

Press d on a remote session. Navi updates the remote status file to set status to "working", clear the message, and update the timestamp.

Press f to cycle through session filter modes:

ModeSessions Shown
AllLocal and remote
LocalOnly local sessions
RemoteOnly remote sessions

The current filter is shown in the footer.

On startup, navi reads ~/.config/navi/remotes.yaml and creates an SSHPool with a connection entry for each configured remote. The pool:

  • Establishes SSH connections on first use (lazy connection)
  • Reuses existing connections across multiple commands via Go’s x/crypto/ssh client
  • Detects dead connections with keepalive probes and automatically reconnects
  • Supports jump host/bastion routing by chaining SSH connections

Connection constants:

  • Connect timeout: 10 seconds
  • Command timeout: 30 seconds

All configured remotes are polled in parallel. For each remote:

  1. Execute cat <sessions_dir>/*.json over SSH
  2. Parse the concatenated JSON output (handles multiple JSON objects in sequence)
  3. Tag each session with the remote name (Info.Remote field)
  4. Merge into the main session list

If a remote is unreachable, its sessions are simply absent — the dashboard continues working with the remaining remotes.

Each remote tracks its connection status:

StatusMeaning
connectedSSH connection active
disconnectedNot yet connected or cleanly disconnected
errorConnection failed (last error recorded)
  • SSH key authentication only — no password storage
  • Jump host support for bastion access patterns
  • Shell arguments are properly quoted to prevent injection
  • Host key checking is currently permissive (InsecureIgnoreHostKey)

”Warning: failed to load remotes config”

Section titled “”Warning: failed to load remotes config””

The ~/.config/navi/remotes.yaml file exists but has a syntax error or is missing required fields. Check that all remotes have name, host, user, and key fields.

  • Verify SSH key authentication works: ssh -i <key> user@host "cat ~/.claude-sessions/*.json"
  • Check that Claude Code sessions are running on the remote and the hooks are installed there
  • Verify the sessions_dir path is correct (must match where hooks write on the remote)
  • Connection timeout is 10 seconds — slow networks may need investigation
  • If using a jump host, ensure both the jump host and target are reachable
  • Check that the SSH key file exists and has correct permissions (chmod 600)