Debugging the session picker
The session picker is built from one command Moshi runs over SSH at connect time. When your sessions are missing, run that same command by hand — it fails in a handful of specific, fixable ways.
You have tmux sessions running on the host. tmux ls inside the terminal lists them. But when you tap a saved connection, Moshi drops you straight into a shell — or opens the picker with only Recent directories and no tmux tab.
This page walks through why, in the order worth checking.
The picker does not use the daemon
This is the most common wrong turn. Moshi has two different multiplexer surfaces, and they get their data from completely different places:
- The connect-time session picker runs one shell command over your SSH connection, before any terminal exists. It never talks to
moshi-hook. - The in-terminal workspace switcher (Jump To, the session tree) reads
/v1/workspacesfrom the host gateway, which is the daemon.
So if the picker is empty, moshi-hook status, the daemon log, and the multiplexers (daemon) section tell you nothing about it. A host with the daemon stopped entirely still shows a full session picker. Conversely, a working picker does not prove the gateway is healthy — for that, see Debugging the gateway.
Reproduce the probe by hand
At connect time Moshi runs a single POSIX shell script over SSH that checks each multiplexer and lists its sessions. This is the short version — enough to reproduce every failure below:
$ssh myhost "sh -lc 'command -v tmux; tmux list-sessions'"/usr/bin/tmuxmain: 2 windows (created Wed Aug 5 06:13:22 2026) (attached)
Two details matter and are easy to miss:
- It runs
sh -lc, not your login shell. On most Linux distributions/bin/shis dash, not bash. - Moshi prepends a fixed list of common install directories to
PATH—~/.local/bin,~/bin, linuxbrew, nix profiles,/opt/homebrew/bin,/usr/local/bin,/opt/local/bin,/usr/bin,/bin— and only then appends whateverPATHthat shell already had.
That prepend is the part people leave out, and it changes the answer. Your own PATH comes last, so a tmux in /usr/bin wins over one in ~/miniforge3/bin — the opposite of what your shell does. A test without the prepend can pass while the picker fails.
So the faithful reproduction sets the same PATH the app does:
$ssh myhost "sh -lc 'export PATH=\$HOME/.local/bin:\$HOME/bin:/usr/local/bin:/usr/bin:/bin:\$PATH; command -v tmux; tmux -V; tmux list-sessions'"/usr/bin/tmuxtmux 3.2a
If that prints your sessions, detection is fine and you should skip to Timing and Channel limits. If it prints a different tmux than the one your sessions run under, go to Two tmux installations. If it prints nothing at all, keep reading.
PATH: the probe shell is not your shell
Testing with ssh myhost 'command -v tmux' is not the same test. That runs your login shell — and bash sources ~/.bashrc for non-interactive SSH commands. The picker's sh -lc does not.
A login sh reads /etc/profile and ~/.profile. It does not read ~/.bashrc, and on Ubuntu the stock ~/.profile only sources ~/.bashrc when the shell is bash. So any tool installed somewhere that only ~/.bashrc puts on PATH is invisible to the probe.
This bites hardest with per-user package managers that write their init block into ~/.bashrc:
- conda, miniforge, mamba (
~/miniforge3/bin,~/anaconda3/bin) - nvm-managed Node prefixes
- asdf, mise, pyenv shims
- a manually built tmux in a custom prefix
Compare the two shells to confirm:
$ssh myhost 'command -v tmux'/home/you/miniforge3/bin/tmux$ssh myhost "sh -lc 'command -v tmux'"# no output → the picker cannot see your tmux either
The fix is to put the binary somewhere the probe already looks. Either install the distro package so it lands in /usr/bin, or symlink it into ~/.local/bin, which is first on the probe's PATH:
$mkdir -p ~/.local/bin$ln -sf "$(command -v tmux)" ~/.local/bin/tmux$ssh myhost "sh -lc 'command -v tmux'"# should now print /home/you/.local/bin/tmux
The same applies to zellij and herdr. Reconnect after the change — the probe runs once per connect.
Two tmux installations
A second copy of tmux is easy to end up with — Homebrew alongside the distro package, or conda's tmux alongside apt's. Because the probe searches its own prepended PATH before yours, it resolves a different binary than your interactive shell whenever the second copy sits in one of those directories. A conda tmux is the common case: it lives at the end of your PATH, so /usr/bin/tmux beats it for the probe and loses to it for you.
That by itself would be harmless. The problem is that tmux refuses to talk to a server started by a different version, so the probe asks the wrong tmux and gets an error rather than your sessions:
$ssh myhost 'tmux -V'tmux 3.4$ssh myhost "sh -lc 'tmux -V; tmux list-sessions'"tmux 3.2aprotocol version mismatch (client 8, server 7)
The picker treats that error the same as an empty list, so the tmux tab simply does not appear. List every copy on the host:
$ssh myhost "sh -lc 'command -v -a tmux'"/usr/bin/tmux/opt/homebrew/bin/tmux
Two or more lines means this is your problem. Either remove the copy you do not want, or point the probe at the one you actually use:
$mkdir -p ~/.local/bin$ln -sf /opt/homebrew/bin/tmux ~/.local/bin/tmux
Sessions already running under the other binary stay unreachable until that server exits — the version mismatch cuts both ways. Restart those sessions, or make this change before starting the ones you care about.
Timing
The picker probe has a time budget. If it expires, Moshi continues without multiplexer sessions and shows a Session check timed out warning toast.
The budget adapts per saved connection: a first-time connection gets a cold ceiling of about four seconds, a host with history gets roughly two and a half times its recent average, and repeated timeouts back off further, up to ten seconds. So a host that times out once usually recovers by itself on the next attempt.
Persistent timeouts almost always mean a slow login shell rather than a slow network. Measure it without the SSH handshake, which the app does not pay — it reuses one authenticated connection:
$ssh -o ControlMaster=auto -o ControlPath=/tmp/moshi-cm -o ControlPersist=60 myhost true$time ssh -o ControlPath=/tmp/moshi-cm myhost "sh -lc 'command -v tmux >/dev/null'"# under ~0.5s is healthy; multiple seconds means your profile is doing heavy work
If that is slow, something in /etc/profile, /etc/profile.d/*.sh, or ~/.profile is expensive — a conda hook that spawns Python, a version-manager init, an NFS-backed home directory, or a corporate login script. Moving heavy work behind an interactive-shell guard speeds up the picker and every other remote tool.
Channel limits
Moshi opens one SSH connection and runs its connect-time probes over it concurrently — the multiplexer snapshot, the recent-directories lookup, and the moshi-hook status check. If your host limits how many sessions a single connection may open, the extra channels are refused, and a refused probe looks exactly like "no multiplexer installed".
A one-command-at-a-time test never reproduces this. Test the concurrent case explicitly:
$ssh -o ControlMaster=auto -o ControlPath=/tmp/moshi-cm -o ControlPersist=60 myhost true$for i in 1 2 3; do ssh -o ControlPath=/tmp/moshi-cm myhost "sh -lc 'command -v tmux'" & done; wait# expect three paths; fewer means channels are being refused
Then check the server-side cap:
$ssh myhost 'sshd -T 2>/dev/null | grep -iE "maxsessions|maxstartups"'maxsessions 10
maxsessions 10 is the default and is fine. A hardened MaxSessions 1 is not — raise it in /etc/sshd_config, or in /etc/ssh/sshd_config.d/, and reload sshd. If you have a host where the picker works and one where it does not, comparing this value between them is often the fastest way to settle it.
Sessions are listed but greyed out
That is entitlement, not detection. Selecting a multiplexer session is a Pro feature with a limited free trial; when the trial is used up the picker still shows every session, with a lock banner reading Multiplexer sessions are Pro features. See Subscription.
Detection failures look different: the tab for that multiplexer is missing entirely, or the picker never opens.
The picker never opens at all
Moshi only shows the picker when there is something to pick. With no multiplexer sessions and no recent directories, it connects straight to a plain shell — the same behavior as a host with nothing running on it.
So an empty picker and a skipped picker are the same underlying result: the probe came back with nothing. Two settings also shape what appears, under Settings -> Shell -> Recent directories:
Recent directories come from moshi-hook cwd-list --json when the daemon is installed, with a POSIX shell fallback when it is not — so that tab, unlike the multiplexer tabs, is affected by the hook.
Zellij and Herdr specifics
- Zellij is listed through
zellij list-sessions, tried in three flag variants for version compatibility. Ifzellij lsworks interactively but the picker is empty, run the probe form:ssh myhost "sh -lc 'zellij list-sessions --short'". - Herdr sessions come from
herdr session list --json, and only sessions whose server is actually running are shown — a stopped default session is deliberately hidden, matching tmux semantics. When exactly one Herdr session exists, the picker collapses it and lists that session's workspaces directly.
Common failures
- tmux works by hand, nothing in the picker — compare
ssh host 'command -v tmux'againstssh host "sh -lc 'command -v tmux'". Different results mean PATH; see above. - Both shells find tmux, still nothing in the picker — check for a second install with
command -v -a tmux. Two copies means a protocol version mismatch against the running server; see Two tmux installations. - Works on one host, not another, same app and account — compare
sshd -T | grep maxsessionsand the login-shell timing between the two hosts. - "Session check timed out" toast — slow login shell. Reconnect once (the budget adapts upward), then fix the profile.
- Sessions appear, then vanish on a later connect — the tmux server exited. Confirm with
tmux lson the host; sessions do not survive a host reboot unless something recreates them. - Sessions listed but not selectable — Pro trial exhausted, not a detection problem.
- Picker fine, but Jump To or the workspace switcher is empty — that surface does use the daemon. Go to Debugging the gateway.
Related docs
- Multiplexers — the support matrix across tmux, Zellij, and Herdr.
- tmux — setup and day-to-day use.
- Debugging the gateway — for anything that happens after the terminal is open.
- Troubleshooting — general connection problems.