@@ -78,9 +78,22 @@ pub fn build_probe_argv(ssh_target: &str, timeout_secs: u64) -> Vec<String> {
7878 // `codemux-remote version` if available. The remote-side
7979 // `printf` separates the two with a sentinel so the laptop
8080 // can split.
81+ //
82+ // The `$HOME/.local/bin/codemux-remote` fallback exists because
83+ // bootstrap installs there, but a non-interactive SSH shell
84+ // (which is what `ssh user@host 'cmd'` runs) typically does NOT
85+ // source `~/.profile` — so `~/.local/bin` is missing from PATH
86+ // on most distros (Arch, Ubuntu, Debian, Fedora). Without the
87+ // fallback, `command -v` returns nothing immediately after a
88+ // successful install and the UI re-shows the install button on
89+ // every subsequent "Test connection" press. Mirrors the same
90+ // fix applied in `commands::hosts::ensure_remote_binary_current`
91+ // and the supervisor's tunnel-spawn command.
8192 "printf 'UNAME: ' ; uname -sm ; \
8293 if command -v codemux-remote >/dev/null 2>&1 ; then \
8394 printf 'CMR: ' ; codemux-remote version ; \
95+ elif [ -x \" $HOME/.local/bin/codemux-remote\" ] ; then \
96+ printf 'CMR: ' ; \" $HOME/.local/bin/codemux-remote\" version ; \
8497 else \
8598 printf 'CMR: NOT_INSTALLED\\ n' ; \
8699 fi"
@@ -185,6 +198,37 @@ mod tests {
185198 assert ! ( argv. last( ) . unwrap( ) . contains( "codemux-remote" ) ) ;
186199 }
187200
201+ #[ test]
202+ fn build_probe_argv_falls_back_to_home_local_bin ( ) {
203+ // Bootstrap installs to ~/.local/bin/codemux-remote, but a
204+ // non-interactive ssh shell typically doesn't have that dir on
205+ // PATH (it's added by ~/.profile, which only login shells
206+ // source). Without an explicit fallback, `command -v` returns
207+ // nothing immediately after a successful install and the UI
208+ // re-shows the install button on every "Test" press.
209+ //
210+ // Lock in BOTH the PATH lookup AND the absolute-path fallback
211+ // so a future refactor doesn't silently drop either branch.
212+ let argv = build_probe_argv ( "zeus@10.0.0.5" , 8 ) ;
213+ let cmd = argv. last ( ) . unwrap ( ) ;
214+ assert ! (
215+ cmd. contains( "command -v codemux-remote" ) ,
216+ "PATH lookup must remain (fast path when binary is on PATH)"
217+ ) ;
218+ assert ! (
219+ cmd. contains( "$HOME/.local/bin/codemux-remote" ) ,
220+ "absolute-path fallback must remain (covers the common case \
221+ where ~/.local/bin isn't on the non-interactive PATH)"
222+ ) ;
223+ // The fallback must run the binary, not just stat it — otherwise
224+ // we'd report "installed" for a 0-byte file or a half-uploaded
225+ // binary that doesn't actually execute.
226+ assert ! (
227+ cmd. contains( "\" $HOME/.local/bin/codemux-remote\" version" ) ,
228+ "fallback must invoke the binary's version subcommand"
229+ ) ;
230+ }
231+
188232 #[ test]
189233 fn parse_probe_stdout_reachable_with_installed_binary ( ) {
190234 let payload = r#"UNAME: Linux x86_64
0 commit comments