I recently discovered that launchd on macOS provides an initial, default $PATH to new terminal windows that is not the default path set by macOS at log in, but instead is produced by launchd according to (presumably) its own internal logic — without changes ever having been made via either launchctl or any .plist file that I could find.
This is the case whether using the Terminal.app distributed by Apple, or a third-party terminal such as Alacritty.app.
The default path on macOS at log in, as can be verified with sysctl -n user.cs_path, is
/usr/bin:/bin:/usr/sbin:/sbin But when I placed echo $PATH on the first line of ~/.zshenv, which is sourced before /etc/zprofile and any other shell configuration file, I was seeing an entirely different $PATH when opening a new window in Alacritty:
/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin Which I verified to be coming from launchd by searching the output of launchctl dumpstate:
pid/72911 = { type = pid originator = /Applications/Alacritty.app creator = alacritty[72911] ... environment = { PATH => /usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin ... } ... } After restarting Alacritty, with only one window and one instance of the shell the same echo command produced:
/usr/bin:/bin:/usr/sbin:/sbin Which is what it should be. But then, opening the default Terminal.app, I get
/usr/bin:/bin Which is, again, not the OS default.
This doesn’t necessarily affect my ability to use the shell because I can set $PATH in .zshenv or .zprofile to whatever I’d like, but it does require that I override the $PATH that launchd sets if I wish to control search order. In fact, what launchd is doing makes controlling search order crucial because there is apparently no guarantee that the initial $PATH handed to the shell will be consistent.
Does anybody know why launchd is providing an inconsistent $PATH? Is there any rhyme or reason to what it decides to include or not include in the $PATH that it provides to every shell created with every new terminal window, and varying it by terminal application?
Again, I have never
- issued
launchctl setenv PATH <path> - issued either
sudo launchctl config user <path>orconfig system <path> - modified any
.plistfile anywhere (or found any that set thePathEnvironmentVariablekey).
I am running macOS Sequoia 15.4 (24E248) on an M2 MacBook Pro, using the stock Zsh shell.
The closest answer I could find mentions that launchd manipulates the default $PATH, but the author doesn’t specify how or why.
Update 1
For the stock Terminal app, launchctl dumpstate is showing
pid/45557 = { type = pid originator = /System/Applications/Utilities/Terminal.app creator = Terminal[45557] ... environment = { PATH => /usr/bin:/bin:/usr/sbin:/sbin ... } ... } Which is indeed the correct, default $PATH. However, for the stock Terminal app, an echo $PATH statement on the first line of .zshenv continues to result in the truncated
/usr/bin:/bin Update 2
The developers working on Alacritty have told me that they do not touch the $PATH variable.
Update 3
With echo $PATH as the first line in .zshenv producing
/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin in Alacritty, and
/usr/bin:/bin in Terminal, subsequently executing
/usr/bin/env -i /bin/zsh -l -osourcetrace per Gairfowl’s suggestion (see comments) shows that same echo command outputting a completely different $PATH in both Alacritty and Terminal:
+/Users/Me/.zshenv:1> <sourcetrace> # line 1 /bin:/usr/bin:/usr/ucb:/usr/local/bin # line 2 `echo $PATH` There is no /usr/ucb directory or file, hidden or not, that I could find. But given that a Google search for “ucb” tells me it is a reference to "University of California, Berkeley" (and that the output is from env -i zsh), could it be Zsh itself is manipulating the initial $PATH? And then either it or something else in macOS is inconsistently transforming /usr/ucb into one of these not-configured paths before completing echo $PATH on the first line of .zshenv, and sometimes informing launchd of the change (causing it to appear in the launchctl dumpstate output)?
Note that those two lines from the env output are printed before /etc/zprofile is sourced, thus Apple's path_helper utility is not yet in play.
I should point out that in all my examples thus far the shell has been consistently in both login and interactive states, verified with setopt.
Update 4
The developers working on Alacritty generously pointed me to the section of their source code responsible for launching new shells (lines 131 through 150).
What it shows is, essentially (when zsh is the default shell),
/usr/bin/login -flp "$USER" /bin/zsh "-c exec -a -zsh /bin/zsh" To paraphrase all the man pages, login -p will enter a $PATH into the new shell environment that it gets from pam(3) via environ(7) and execve(2).
The man page for environ(7) explains that login(1), when -p is absent, sets the $PATH to:
/usr/bin:/bin Which may explain what I’m seeing in Terminal.app (the source code for which I probably can’t peruse). But this doesn’t account for what echo $PATH is printing when in Alacritty, where login -p is being called:
/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin Or what /usr/bin/env -i /bin/zsh -l -osourcetrace outputs in both Alacritty and Terminal:
/bin:/usr/bin:/usr/ucb:/usr/local/bin At this point my only guess is that pam_launchd.so (listed in /etc/pam.d/login) is where the uncertain and inconsistent initial $PATH, as printed by echo $PATH when in Alacritty, is coming from. But I don't know. And I certainly don't know why.
As for the odd (albeit consistent) /usr/bin/env -i /bin/zsh -l -osourcetrace output including /usr/ucb — a very old default leaking out of but ultimately overridden by Zsh?
Point of clarification
The example I’ve given of the path output by echo $PATH at the top of .zshenv when in Alacritty (thus acquired via login -p),
/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin:/usr/sbin:/sbin is an example. It’s not consistent. Sometimes it is that path, sometimes it is
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin and occassionally it is
/usr/bin:/bin:/usr/sbin:/sbin What I would like to know is what it will be tomorrow, why, and whether I can lock it down without overriding it.
launchdisn't settingPATH; that's not what your output shows. "Alacritty" is setting it for itself, likely because that's what is in its settings or its hard-coded defaults. Other ways forPATHto be set are in the shell initialization files, in/etc/paths, and in/etc/paths.d. If Terminal is giving you a differentPATHfor the same shell, the difference is to be found either in its settings or in those of Alacritty/usr/bin/env -i /bin/zsh -l -osourcetrace; the call to/usr/libexec/path_helperin/etc/zprofilemay be significant. I think Terminal launches each new tab as a 'login' shell (the-l); Alacritty might not be doing that.launchctl dumpstatereports for the stock Terminal app. With respect to Alacritty, I asked its developers and was told that they “do not touch” the variable. I certainly couldn’t find any setting in the Alacritty configuration that might modify the initial$PATH.