“No such executable in PATH: (zsh)” error when loading extension

I am seeing this problem on the trial version of PopClip 2024.3.2 (4516). I’m on macOS 14.4.1.

It’s probably related to the fact that my default shell is /opt/homebrew/bin/fish. Still, /bin/zsh exists and works and /bin is in my PATH.

1 Like

Hi Doug, welcome to the forum. I’d like to look into it. Can you tell me the specific extension this is happening with?

I’m trying the cut-and-paste way of installing one of the sample snippets from the PopClip website:

I also tried it with the Say extension:

Screenshot 2024-05-06 at 16.15.07

These Say extensions assume that they can find the zsh executable in your user PATH. Usually zsh is in /bin so it should find it if you have /bin in your path.

If you type echo $PATH in terminal you can check that.

Another possibility is that /bin is in your path but zsh executable does not exist there.

Are either of these the case on your machine?

If zsh is there and the PATH is set correctly, it might indeed be somehting about the default shell configuration. Maybe PopClip is struggling to load the PATH at all. Please could you check the PATH setting & presence of zsh first, and then once that is eliminated I can dig in more deeply.


…Alternatively you can just hardcode an interpreter in the extension via an absolute path though this is of course less portable:

#popclip shellscript example  
name: Say
interpreter: /opt/homebrew/bin/fish
shell script: say -v Daniel $POPCLIP_TEXT

The local installation of zsh seems pretty standard and functional:

dnsimon@blitz ~> zsh
dnsimon@dnsimon-mac ~ %
dnsimon@blitz ~> ls /bin/zsh
/bin/zsh*
dnsimon@blitz ~> zsh
dnsimon@dnsimon-mac ~ %

Using the hardcoded path to /opt/homebrew/bin/fish indeed makes the installation work. Maybe PopClip simply does not work for a user whose default shell is fish (which would be a shame).

Let’s dig into it. Here’s the objective-c code that finds the PATH string:

+ (NSString *)userPath {
    // get user path
    static NSString *result;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        if (@available(macOS 10.13, *)) {
            NSString *const userShell = [[[NSProcessInfo processInfo] environment] objectForKey:@"SHELL"];
            NMLogInfo(@"User's shell is %@", userShell);
            
            // avoid executing stuff like /sbin/nologin as a shell
            BOOL isValidShell = NO;
            for (NSString *validShell in [[NSString stringWithContentsOfFile:@"/etc/shells" encoding:NSUTF8StringEncoding error:nil] componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]) {
                if ([[validShell stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] isEqualToString:userShell]) {
                    isValidShell = YES;
                    break;
                }
            }
            
            if (!isValidShell) {
                NMLogError(@"Shell %@ is not in /etc/shells, won't continue.", userShell);
                return;
            }
            
            NSString *const userPath=[self runTool:[NSURL fileURLWithPath:userShell] withArguments:@[@"-lc", @"echo $PATH"]].stringByTrimmingWhitespaceAndNewlines;
            if (userPath.length > 0 && [userPath rangeOfString:@":"].length > 0 && [userPath rangeOfString:@"/usr/bin"].length > 0) {
                NMLogImportant(@"User's PATH as reported by %@ is %@", userShell, userPath);
                result=userPath;
            }
            else {
                NMLogError(@"Path task error");
            }
        }
    });
    return result;
}

To break that down:

  • first it checks its own environment variables for the key SHELL. On my machine:
> echo $SHELL
/bin/zsh

and I would guess on your machine that command would return /opt/homebrew/bin/fish instead.

  • then PopClip loads the file /etc/shells and checks if the specified shell is in that list. On my machine:
> cat /etc/shells         
# List of acceptable shells for chpass(1).
# Ftpd will not allow users to connect who are not using
# one of these shells.

/bin/bash
/bin/csh
/bin/dash
/bin/ksh
/bin/sh
/bin/tcsh
/bin/zsh

I’m guessing this is the bit where things are going wrong on your machine. Since /opt/homebrew/bin/fish is not in there, PopClip is noping out at this point.

  • then PopClip performs a simple sanity check on the purported PATH string to see if it looks like it should

  • if valid PopClip calls that shell with the script echo $PATH and captures the output. (It then uses this to find the interprerer via which.)


So as mentioned above, I’m pretty sure it’s choking at the list of shells. I reckon if you edited that file to add your shell path to the list, then PopClip would be able to use that default shell. (I tried editing mine, i had to use sudo and a special macOS authenticasion thing but it I could edit it)

So I guess for now that’s the workaround.

I’ll have a think if there is a better way to PopClip to do this so in future it could support a homebrew installed shell out of the box. ATM I’m thinking I could just drop the check against the shell list, which is not really relevant to PopClip. I got that from some example code and it seemed a good idea to keep it. In this particular case, I don’t think calling /sbin/nologin -lc "echo $PATH" actually does any harm. It just returns with an error status.

Further update: I’ve released beta Build 4575, which drops the /etc/shells check and therefore ought to work on your machine out of the box. When you get a chance to try it, please let me know if it solves it for you.

I updated /etc/shells to add /opt/homebrew/bin/fish but it did not help.
I tried with both build 4516 and build 4575.

I logged in with another account on my machine that still has /bin/zsh as the default account and extension installation worked. So fish is definitely causing some kind of problem.

Thanks for trying. I’ll try actually installing Homebrew fish as my default shell and see what’s going on.

First of all, fish is a rather nice shell!

Second, I figured out it’s specifically a fish issue. It handles its PATH in a non-POSIXy way, different to other shells, and the PATH variable coming back from it was incompatible with the way I was doing the which call.

But I do want PopClip to just work regardless of what shell is default.

So I have re-done everything to not explictly depend on the PATH, and instead call every script via the current default shell as “host” executable, with the interpreter and script passed a command to that using the -lc flags. This means it gets run in the context of a login shell.

In summary, this time it should work, with Build 4578.

Yes, very nice, build 4578 works! Thanks for the quick turnaround on this.

1 Like