When opening terminal applications, launchd is setting the initial $PATH inconsistently. Why?
3
votes
1
answer
239
views
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
...
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*
1. issued launchctl setenv PATH
2. issued either sudo launchctl config user
or config system
3. modified any .plist
file anywhere (or found any that set the PathEnvironmentVariable
key).
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
...
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](https://github.com/alacritty/alacritty/issues/8535) 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](https://apple.stackexchange.com/users/274760/gairfowl)’s suggestion (see comments) shows that same echo
command outputting a completely different $PATH
in both Alacritty and Terminal:
+/Users/Me/.zshenv:1> # 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](https://github.com/chrisnc/alacritty/blob/6566dd3defa9f080dabb295740dc1dac06e3b8fb/alacritty_terminal/src/tty/unix.rs#L131-L150) 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.
Asked by Absolute Input
(39 rep)
Apr 6, 2025, 10:50 PM
Last activity: Apr 8, 2025, 10:10 AM
Last activity: Apr 8, 2025, 10:10 AM