I’ve been experimenting with Guix on an unused Intel NUC that’s been collecting dust in my office. The Iinstallation process was easy but remotely accessing the host has been a problem. ssh-ing to the host works fine but I can’t open remote files over ssh via tramp in Emacs. Every time I try Emacs complains Can't find proper ls and refuses to open the file. Argh.

tl;dr Add (add-to-list 'tramp-remote-path 'tramp-own-remote-path) to your Emacs config if tramp can’t find ls on a Guix-based host.

Keep reading if you’re curious about the process I used to debug my config.

First, I tried to manually reproduce how I thought tramp was interacting with the NUC: ssh user@guix_host "ls /". No joy, just a listing of the root directory.

Is tramp using a restricted shell? I repeat the same test running ls inside of another /bin/sh instance like so: ssh user@guix_host "sh -c -r \"ls /\"". It works. Drat!

Next, I enabled verbose tramp logging by setting tramp-verbose to 11 and tried opening a remote file again. Afaik this is the only way to see the low level back and forth between tramp and a remote host. WARNING: This will produce a lot of output and most of it you won’t need. Searching for any occurence of ls I discovered tramp was raising an exception in tramp-get-ls-command.


I freshened my local clone of Emacs – I’m using a custom build off the emacs-29 branch – and started looking through tramp’s source for the function’s definition. Some grepping led me to lisp/net/tramp-sh.el. Tramp uses a list of paths stored in tramp-remote-path to locate commands on a remote host. Let’s try (add-to-list 'tramp-remote-path "/run/current-system/profile/bin").

Same error. Digging more deeply into how tramp constructs remote paths I discover tramp-own-remote-path:

Another way to find the remote path is to use the path assigned to the remote user by the remote host. TRAMP does not normally retain this remote path after login. However, tramp-own-remote-path preserves the path value, which can be used to update tramp-remote-path.

This sounds promising.

I know Guix uses shell profiles to set up user search paths. Tramp’s default behavior would be to discard those paths which could account for the error. Following the docs I add (add-to-list 'tramp-remote-path 'tramp-own-remote-path), restart Emacs, and try again. It works!