You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
run_command: tmux completion wrapper breaks over SSH panes (error connecting to socket) #132
run_command wraps each authored command with a completion-sync wrapper (see src/libtmux_mcp/tools/pane_tools/io.py, the status_cmd / signal_cmd
construction):
The tmux set-option and tmux wait-for -S fragments execute inside the
pane's shell. When the pane's shell is an active SSH session, that shell
lives on a remote host that has no tmux server (or a different socket), so both
calls fail in the pane with:
error connecting to /tmp/tmux-1001/default (No such file or directory)
error connecting to /tmp/tmux-1001/default (No such file or directory)
(the socket path carries the remote user's uid).
Steps to reproduce
In a tmux pane, run ssh user@remote-host and log in.
Have the agent call run_command in that pane (e.g. git status).
Observe the two error connecting to .../default lines printed in the pane.
Actual behavior
The user's command still runs and its output is visible, so it looks like it
"works".
But the wait-for channel is never signalled, so the server-side wait runs to
the LIBTMUX_MCP_WAIT_MAX_SECONDS ceiling and the call reports a
timeout-based result.
The @s_<id> pane option is never set, so no exit status can be read back.
The error lines are printed into the pane a human may be watching.
Expected behavior
One or both:
Document the limitation — gotchas.md, troubleshooting.md, and/or the run_command tool description: run_command assumes the pane's shell can
reach the same tmux server the MCP talks to. In SSH panes, compose send_keys + a shell-level completion marker + wait_for_text + capture_pane instead.
Detect it — e.g. check pane_current_command (or pane_in_mode) before
wrapping: when the pane is ssh, either skip the wrapper and warn in the
result, or fail loudly with a hint, rather than sending a wrapper that is
guaranteed to emit errors on the remote.
Workaround that works (shell marker, no tmux in the payload)
The same failure reproduces on one host with no SSH. What the wrapper needs is for the pane's shell to resolve the same socket the server does, and a remote filesystem is only one way to lose that. A different $TMUX_TMPDIR in the pane's shell does it too.
Two arms, identical except for that variable:
# Both arms of the wrapper, on one host, no SSH.
SERVER_TMPDIR=$(mktemp -d /tmp/srv.XXXX); PANE_TMPDIR=$(mktemp -d /tmp/pane.XXXX)srv() { TMUX_TMPDIR=$SERVER_TMPDIR tmux -L probe "$@"; }
srv new-session -d -s s -x 200 -y 50
sleep 1
# Arm 1: the pane's shell shares the server's TMUX_TMPDIR.
srv send-keys -t %0 "tmux -L probe set-option -p -t %0 @arm_control ok" Enter
# Arm 2: only the pane's shell has a different TMUX_TMPDIR.
srv send-keys -t %0 \
"TMUX_TMPDIR=$PANE_TMPDIR tmux -L probe set-option -p -t %0 @arm_test ng" Enter
srv send-keys -t %0 \
"TMUX_TMPDIR=$PANE_TMPDIR tmux -L probe wait-for -S ch_test" Enter
sleep 2
echo"--- what the pane printed ---"
srv capture-pane -p -t %0 | grep 'error connecting'echo"--- options that got set ---"
srv show-options -p -t %0 | grep @arm
echo"--- does wait-for ever signal? ---"
TMUX_TMPDIR=$SERVER_TMPDIR timeout 3 tmux -L probe wait-for ch_test
echo"wait-for exit: $? (124 = timed out)"
srv kill-server; rm -rf "$SERVER_TMPDIR""$PANE_TMPDIR"
--- what the pane printed ---
error connecting to /private/tmp/pane.vZw4/tmux-501/probe (No such file or directory)
error connecting to /private/tmp/pane.vZw4/tmux-501/probe (No such file or directory)
--- options that got set ---
@arm_control ok
--- does wait-for ever signal? ---
wait-for exit: 124 (124 = timed out)
Arm 1 sets its option. Arm 2 prints error connecting, never sets @arm_test, and its wait-for -S never reaches the server, so the matching wait-for on the server side runs to the timeout.
_tmux_argv carries -L/-S into the pane, which pins which socket is asked for. Resolving that name is still done by the pane's shell, against its own $TMUX_TMPDIR, its own euid, and its own filesystem. Any of the three can differ from the server's:
an SSH session: different filesystem (this report)
a different $TMUX_TMPDIR: above
a shell under another uid, since the path carries tmux-<euid>
a container shell (docker exec): same as SSH
The outcome is the same in each. The authored command runs, its output is visible, and only the completion signal is lost — so run_command waits out the ceiling before reporting anything, and the two error connecting lines are the only clue, sitting in the pane's scrollback rather than in the tool's result.
One note on detection: #{pane_current_command} catches ssh and docker, but not the $TMUX_TMPDIR case, where the pane's shell is an ordinary local shell.
Activity
isamu commentedon Sep 18, 2026
The same failure reproduces on one host with no SSH. What the wrapper needs is for the pane's shell to resolve the same socket the server does, and a remote filesystem is only one way to lose that. A different
$TMUX_TMPDIRin the pane's shell does it too.Two arms, identical except for that variable:
Arm 1 sets its option. Arm 2 prints
error connecting, never sets@arm_test, and itswait-for -Snever reaches the server, so the matchingwait-foron the server side runs to the timeout._tmux_argvcarries-L/-Sinto the pane, which pins which socket is asked for. Resolving that name is still done by the pane's shell, against its own$TMUX_TMPDIR, its own euid, and its own filesystem. Any of the three can differ from the server's:$TMUX_TMPDIR: abovetmux-<euid>docker exec): same as SSHThe outcome is the same in each. The authored command runs, its output is visible, and only the completion signal is lost — so
run_commandwaits out the ceiling before reporting anything, and the twoerror connectinglines are the only clue, sitting in the pane's scrollback rather than in the tool's result.One note on detection:
#{pane_current_command}catchessshanddocker, but not the$TMUX_TMPDIRcase, where the pane's shell is an ordinary local shell.