PATH issues from using tmux on macOS

If you’re a macOS user with bash, sh, or ksh as your login shell, then the value of the environment variable PATH may not be what you expect after you invoke tmux from your login shell in Terminal.

Problem

Let’s assume your login shell is bash. Upon launch, Terminal invokes bash as an interactive login shell and hence executes the system-wide configuration sh script /etc/profile 1 2, which contains an instruction to execute path_helper.

/etc/profile

# System-wide .profile for sh(1)

if [ -x /usr/libexec/path_helper ]; then
	eval `/usr/libexec/path_helper -s`
fi

if [ "${BASH-no}" != "no" ]; then
	[ -r /etc/bashrc ] && . /etc/bashrc
fi

path_helper appends the path elements found in the file /etc/paths 3 and files in directory /etc/paths.d to PATH.

/etc/paths

/usr/local/bin
/usr/bin
/bin
/usr/sbin
/sbin

These path elements are of directories containing system and common utilities. /etc/paths.d is typically empty and may have contents added to it when you install a package that will affect all users on the system.

Immediately after /etc/profile has executed, PATH should be something like the following:

PATH

/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

Let’s say the only user startup file you have is ~/.profile and it contains the following:

~/.profile

eval "$(/opt/homebrew/bin/brew shellenv)"
PATH="$HOME/.local/bin:$PATH"

When bash launches as an interactive login shell, ~/.profile will be executed after /etc/profile and thus after path_helper.

The first statement adds the installation of Homebrew to PATH. The second statement adds your local bin directory to the front of PATH in order to have priority over the system utilities during command search. 4 Assume $HOME expands to /Users/alfonso.

After ~/.profile’s execution, the path elements

are appended to the front of PATH to yield PATH as the following:

PATH

/Users/alfonso/.local/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

The PATH is all fine and dandy until invoking tmux in the typical manner:

bash

tmux

PATH becomes the following:

PATH

/Users/alfonso/.local/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/alfonso/.local/bin:/opt/homebrew/bin:/opt/homebrew/sbin

As you may have noticed, the path elements

are present in the front and back of PATH. This is not good.

Cause

tmux launches login shells by default. As a result, the startup files are executed again despite the parent shell having already executed them.

/etc/profile executes first and hence path_helper runs. path_helper notices the path elements it intends to append are already present in PATH. PATH already contains the path elements because PATH was inherited from the login shell that invoked tmux. tmux’s login shell is a child process of the login shell that invoked tmux, and therefore environment variables such as PATH are inherited. path_helper strips those path elements from the back of PATH and appends them to the front of PATH to yield the following PATH:

PATH

/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/alfonso/.local/bin:/opt/homebrew/bin:/opt/homebrew/sbin

~/.profile then executes and appends the path elements

to the front of PATH even though they’re already present in PATH, resulting in the following PATH with duplicate path elements:

PATH

/Users/alfonso/.local/bin:/opt/homebrew/bin:/opt/homebrew/sbin:usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Users/alfonso/.local/bin:/opt/homebrew/bin:/opt/homebrew/sbin

Solution

The solution to this problem is to add the following highlighted lines to the beginning of ~/.profile:

~/.profile

if [ -n "$TMUX" ]; then
  PATH=""
  . /etc/profile
fi

eval "$(/opt/homebrew/bin/brew shellenv)"
PATH="$HOME/.local/bin:$PATH"

TMUX is an environment variable initialized by tmux with internal information. If TMUX is a non-zero length string, then we can assume we’re in a tmux environment.

Assuming we’re in a tmux environment, before the execution of the consequent commands (the commands following the then statement and before the associated fi), PATH is the following:

PATH

/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:Users/alfonso/.local/bin:/opt/homebrew/bin:/opt/homebrew/sbin

/etc/profile has already executed in the current tmux environment given that ~/.profile is the startup file that is now executing. That’s why the above PATH is what it is.

By setting PATH to an empty string and then executing /etc/profile in the current shell environment, PATH is stripped of any path elements appended from user startup files and only consists of path elements appended by /etc/profile. After execution of the consequent commands, the remaining commands append the path elements

to PATH to yield the following:

PATH

/Users/alfonso/.local/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin

The PATH is now what we wanted!

Footnotes


  1. For more info about bash startup files, check out this page from the bash manual. ↩︎

  2. bash is capable of executing sh scripts because it is “largely compatible with sh. Although the use of the word largely may not inspire confidence, I and many others have not run into compatiblity issues that were not bugs. ↩︎

  3. macOS is “BSD-inspired”, so this documentation page about the FreeBSD directory structure is helpful to understand the purpose of certain directories. ↩︎

  4. For more about command search and PATH, check out the POSIX standard sections “Command Search and Execution” and “Other Environment Variables” section, respectively. ↩︎