Most projects involve some CLI tools which can be useful to integrate into the IDE. For example, when using an interpreted language like Python, telling the IDE where to find the python executable means it should be able to find and auto-complete language-specific dependencies, detect any syntax errors based on the exact interpreter version, and run the tests without any extra setup. There’s just one snag: some IDEs require you to specify an absolute path to the interpreter, and Nix store paths are hashed:

$ nix-shell --pure --run 'type -a python'
python is /nix/store/y027d3bvlaizbri04c1bzh28hqd6lj01-python3-3.11.7/bin/python

Now, we could absolutely point the IDE to this path, but that’s brittle: if anything changes in the Python derivation, this path will change, and the IDE will no longer be pointing at the right path. We might not even notice until the path is garbage collected from the Nix store.

Here’s the thing, though: Bash knows about python because $PATH contains the Nix store path (/nix/store/y027d3bvlaizbri04c1bzh28hqd6lj01-python3-3.11.7/bin in the example above), and we can use that knowledge to work around IDE deficiencies. If we use the shell to create (or overwrite) a symbolic link to the python executable every time we activate the Nix shell, that link will always point to the relevant file. We can do this in the shellHook:

  pkgs = import ../nixpkgs.nix;
  python = pkgs.python3.withPackages (pythonPackages: [ pythonPackages.pytest ]);
pkgs.mkShell {
  packages = [

  shellHook = ''
    ln --force --no-target-directory --symbolic "${python}/bin/python" python

Now, any time we re-enter this shell, it updates the python symlink1, and we have a fixed absolute path to the “current” Python interpreter! Just point to that file in your IDE, and it should detect the rest. You probably also want to add /python to your .gitignore to make sure it doesn’t get added to version control.

For reference, an example repository which uses this technique.