Skip to content

Profile scripts add duplicate entries to PATH in subshells #5950

@lilyball

Description

@lilyball

Describe the bug

The Nix profile scripts unconditionally add directories to PATH without checking if those directories are already there. The multi-user profile script has a guard variable to prevent it from running multiple times, but this guard variable is not exported and so does not affect subshell behavior. The single-user profile script doesn't even have a guard variable.

The effect of this is that running an interactive subshell (if multi-user, or a login subshell if single-user) will source the profile script and add the directories to PATH again even though they're already there.

Steps To Reproduce

  1. Install Nix in an existing system
  2. Spawn a new shell. Verify that $PATH includes your nix profile bin directories.
  3. Spawn a subshell (a login subshell if you have a single-user install)
  4. Check what $PATH contains

Expected behavior

$PATH should contain your nix profile bin directories exactly once in both the parent shell and the subshell.

Additional context

I have not actually reproduced this problem locally, as I am not currently set up to test standalone Nix installs (the machines I have access to use either NixOS or nix-darwin and I'm not prepared to run a VM at the moment). But this can be verified by reading the profile scripts, and this issue was originally reported to me at lilyball/nix-env.fish#11 in my fish plugin that imports the bash profile.

One potential fix is to just export the __ETC_PROFILE_NIX_SOURCED guard variable (and add this to the single-user profile too as that doesn't even have a guard variable right now), but I don't think that's a good fix as sudo will preserve PATH but not the other variables (and especially not the guard variable) and so running an interactive or login shell via sudo would still add the duplicate PATH entries.

Given that, I think the best approach is to just make sure the profile script is idempotent. I should be able to source it multiple times in a row (unsetting the guard variable each time) and end up with the same environment that I do if I only source it once. The guard variable still has a purpose with this approach, both as an optimization and to prevent re-sourcing the profile from blowing away any subsequent environment modifications (the installer sources the profile from potentially multiple files involved in shell initialization, and I might update the earliest such file to change the environment and so the subsequent re-sourcing of the profile shouldn't blow that away). Making the script idempotent simply requires making it check if the entries are in PATH before setting them, as all other environment changes it does are already idempotent, though it's probably also worth putting a comment in the file noting that it should be idempotent as a caution for anyone modifying it in the future.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions