Inspecting Closures: why-depends, path-info, nix-tree

Updated

September 12, 2026

Inspecting Closures: why-depends, path-info, nix-tree

Shipping a “small” service that secretly contains gcc is a weekly desk accident. The boring default is: measure the closure before you copy it, then ask why-depends when a path you did not expect shows up.

Mental model

Three questions, three tools:

Question Tool
How big is this, including everything it needs? nix path-info -Shr
Why is this path inside that closure? nix why-depends A B
Let me walk the tree with a keyboard nix-tree

nix-store -q --tree is the classic ASCII dump. nix-tree is the same graph with navigation. nom (nix-output-monitor) wraps nix build so you watch fetch/build in a table instead of a scrollback.

You already used --requisites in the closures chapter. This chapter is the daily loop: size, blame, browse.

Worked examples

Case 1: Closure size in one line

nix path-info -Shr nixpkgs#hello

Output (shape):

/nix/store/…-glibc-2.39      31.0M  31.0M
/nix/store/…-hello-2.12.1    226K   31.2M

The last column on the last line is the number you put in a review: hello’s runtime closure is ~31 MB, almost all glibc.

Compare a fat wrapper:

nix path-info -Sh nixpkgs#ripgrep
nix path-info -Shr nixpkgs#ripgrep | tail -1

If the closure is dozens of times the single-path size, something was propagated that should not have been.

Case 2: why-depends

“Why does the desk API closure contain python3?”

nix why-depends nixpkgs#ripgrep nixpkgs#glibc

Output (shape):

/nix/store/…-ripgrep-14.1.0
└───/nix/store/…-glibc-2.39

A longer chain (hello → wrapper → python) prints as a path of edges. That chain is the fix list: cut the edge you did not intend (a propagatedBuildInputs, a wrapProgram that pulled in a huge interpreter, a share/ file that embeds a store path).

If there is no dependence:

nix why-depends nixpkgs#hello nixpkgs#ripgrep

Output:

'hello' does not depend on 'ripgrep'

Case 3: ASCII tree without extra tools

nix-store -q --tree $(nix path-info nixpkgs#hello)

Output (shape):

/nix/store/…-hello-2.12.1
└───/nix/store/…-glibc-2.39
    └───…

Useful on a server where you will not install nix-tree. Pipe to less.

Case 4: nix-tree for interactive blame

nix run nixpkgs#nix-tree -- $(nix path-info nixpkgs#hello)

or, if nix-tree is in your system packages:

nix-tree $(nix path-info nixpkgs#hello)

Keys (typical):

Key Action
j / k Move
enter Open a path’s references
s Sort by size
q Quit

Sort by size first. The top of the list is where the megabytes are.

Case 5: Watch a build with nom

nix run nixpkgs#nix-output-monitor -- build nixpkgs#hello

nom is a frontend. It does not change hashes. Use it when a long nixos-rebuild is a wall of identical “copying path” lines and you want to know whether you are substituting or compiling.

On NixOS you can alias:

# configuration.nix fragment
environment.systemPackages = [ pkgs.nix-output-monitor ];

Then nom build .#desk-api.

The trap

The trap is reading du -sh result. result is a symlink to one path. du does not follow the glibc reference. You report 200 KB, production downloads 31 MB, and someone “optimises” the wrong thing.

Always nix path-info -Shr.

The second trap is why-depends in the wrong direction. nix why-depends A B means “why does A depend on B” — A is the parent closure, B is the unexpected guest.

The boring rule

  • Size with nix path-info -Shr on the output path.
  • Blame with nix why-depends parent child.
  • Browse with nix-tree (or --tree on a server).
  • du on result is not a closure size.
  • nom is optional UX. It is not part of the hash.

Try this

  1. Record nix path-info -Shr nixpkgs#hello and nix path-info -Shr nixpkgs#git. Git is larger. why-depends git against perl or python and see which interpreter, if any, is in the runtime closure.
  2. nix-store -q --tree $(nix path-info nixpkgs#hello) | wc -l — that is the node count of a tiny closure.
  3. Install nix-tree in a devShell, open hello, sort by size, and name the largest path.
  4. Run a nix build twice, once plain and once under nom. Confirm the resulting store path is identical (nix path-info).