Skip to content

Instantly share code, notes, and snippets.

@therealchjones
Last active February 27, 2026 21:48
Show Gist options
  • Select an option

  • Save therealchjones/b74aef4e04849c403401eebcb5ee05bf to your computer and use it in GitHub Desktop.

Select an option

Save therealchjones/b74aef4e04849c403401eebcb5ee05bf to your computer and use it in GitHub Desktop.
Some notes on the environment (especially PATH) in macOS

macOS PATH and Visual Studio Code

As of macOS Monterey 12.2.1 and Visual Studio Code 1.66.0.

The value of macOS's PATH environment variable is set or modified by many different processes. By the time you run programs from the integrated terminal in Visual Studio Code, it's changed several times. Running a task or launching an extension in VS Code makes it even more complicated. Let's take a look.

PATH inheritance

On macOS, each process after booting is started by another process. When creating the "child" process, the "parent" process can choose what environment the child process starts with. Most programs either allow their child processes to "inherit" the same process they started with or start their child processes with "clean" environments with relatively few environment variables set, but nearly any customization of the environment is possible for each child process.

Visual Studio Code is one of the easier applications to see what PATH (or the rest of the environment) was inherited from its starting process: open the Developer Tools console, and enter process.env.PATH.

launchd

The first process on macOS that sets the default environment for later processes is launchd(8). If you've done nothing special to configure it on your system, that includes the environment variable PATH set to /usr/bin:/bin:/usr/sbin:/sbin. Finder, Dock, Spotlight, and many others pass (some of) this environment along to applications they launch, such as when you click on the VS Code icon in Finder or on the Dock. The PATH VS Code inherits is then the same set by launchd. Setting the launchd path for the system is protected (though it can be changed, it's a real pain), but it's easy to set the launchd path for your user: sudo launchctl config user path '/bin:/sbin:/usr/bin:/usr/sbin:/foo' will do the trick, and you can check it by quitting Code, rebooting, and starting Code again.

However, there's more. Applications themselves can run under different environments (including paths) either by changing settings in the Info.plist files in their packages (Code doesn't do this) or being launched from a different environment. So, when run from the Dock (or Spotlight, or Finder, ...), VS Code inherits the launchd environment (which by default has PATH set as above). When run from Terminal, Code inherits whatever environment Terminal's shell currently has set.

Terminal.app and your shell

This means that (at least) the Terminal program and the shell itself have the opportunity to change the PATH before we get to starting Code. It appears that Terminal.app sets the initial PATH to /usr/bin:/bin rather than passing along the PATH it inherited. However, it's difficult to be sure, as for whatever shell (or other program) you've set for Terminal to run, Terminal in reality starts the login(1) program, which then starts the shell. login is set by Terminal to pass along the environment its given, but it may be that Terminal is providing no path at all and that in its absence login sets the PATH to /usr/bin:/bin. In either case, the launchd path does not directly impact the path in your Terminal.

Once the Terminal starts the shell (via login), the shell itself reads the environment it's started with, and may add some default directories to the PATH itself. To see which these are, start the shell in a "clean" environment with flags not to read any startup files. For the default macOS shell zsh, open Terminal and run:

/usr/bin/env -i /bin/zsh -df

Assuming you haven't added a file /etc/zshenv (there's not one by default), this will allow you to run echo $PATH and get the default PATH set by this build of zsh:

$ /usr/bin/env -i /bin/zsh -df
% echo $PATH
/bin:/usr/bin:/usr/ucb:/usr/local/bin

When the shell is started, it will typically read settings from multiple startup files, both system-wide settings and user-specific ones. These vary by shell. However, by default, all shells included in macOS (bash, csh, dash, ksh, sh as a link to bash, tcsh, and zsh) read files in /etc that perform multiple tasks; they all set the PATH using the path_helper(8) program. This program reads the file /etc/paths and those within the /etc/paths.d directory, and uses these files along with the inherited Terminal path (remember, /usr/bin:/bin) to set the PATH; again, by default this ends up being /usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin, but various programs add PATH entries beneath /etc/paths.d (such as the .NET command line tools).

Next, user customizations for the shell process are read via startup files in the user's home directory. Again, these are shell- and user-specific but the PATH can change once again before the shell prompt appears.

Visual Studio Code and the PATH

Visual Studio Code inherits the PATH from wherever it was launched; as noted above, if started from the GUI, it will typically start with the launchd path, but if started from a terminal will inherit whatever the environment running there is. Again, this can be checked from the Developer Tools console as process.env.PATH to confirm. This is usually the path inherited by extensions run within VS Code, though they may request a different environment and can of course change the environment for processes they in turn begin.

Making new processes through VS Code adds another layer of possible changes, and these are affected by several preferences as well. Without considering the plethora of extensions that create or modify processes as well, Code's new processes include the shells (or other programs) started as part of the integrated terminal, tasks, and the "launch" system used for running and debugging.

The VS Code "shell environment"

On macOS, VS Code detects whether it was launched from the command line (e.g., by a different shell) or from the GUI, and adapts the environment used for starting other processes based on this. Much of what happens here is affected by various settings in the VS Code preferences (as well as command-line flags and others), but by default when Code is started from the GUI, it determines the "shell environment" by starting its own copy of a shell, using that shell to start another copy of VS Code, and reading the process.env value from that VS Code process. The shell it uses is intended to be the user's usual preferred shell, but falls back to /bin/bash or sh when that's not easily determined. It runs this shell as an "interactive login" shell, again like Terminal.app, and thus the same startup files read by a shell in Terminal.app are read here and affect this "shell environment". (A couple of other environment variables are added by VS Code, but the PATH and other common settings aren't changed in this process.) The idea is that, where appropriate, new processes will be given the shell environment rather than the startup environment. There is at least one difference between the shell environment and the environment running when using the same shell in Terminal.app: VS Code does not "reset" the PATH to /usr/bin:/bin the way Terminal.app (or login) does, so when VS Code is started from the GUI, the shell environment has a PATH that includes anything set in the launchd PATH. Usually launchd PATH directories are a quite limited set and are already included in /etc/paths, so this is seldom noticeable unless you've changed the launchd PATH directories and haven't changed them elsewhere.

The VS Code integrated terminal

Much like Terminal.app, the integrated terminal typically launches a shell program that can then start other processes. As previously mentioned, this is again affected by various preference settings; some of the defaults for these settings have changed (and even changed back) during VS Code's development, and are likely to change again in the future. However, as of this writing, the current terminal environment is affected by several settings as described below. However, when launching a shell as usual, it is also affected by the shell's startup files as described above.

terminal.integrated.inheritEnv (default: true)

The VS Code preference setting terminal.integrated.inheritEnv is probably one of the most misunderstood and drama-filled settings available in the program. The description is reasonable:

Terminal > Integrated: Inherit env Whether new shells should inherit their environment from VS Code, which may source a login shell to ensure $PATH and other development variables are initialized. This has no effect on Windows

However, it's also reasonable to assume this means that setting it to false will mean the shell is launched with some other kind of environment. Since the shell in the integrated terminal typically uses its startup files to change the environment, it may sometimes appear as though this setting has little effect. In reality, this setting determines whether the shell (or other first process) in the integrated terminal is started with the "shell environment" or the "startup environment" (when terminal.integrated.inheritEnv is set to true or false, respectively).

VS Code Tasks

The VS Code launch system

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment