DEV Community

Asaduzzaman Pavel
Asaduzzaman Pavel

Posted on • Originally published at iampavel.dev

How to Actually Read Nix Error Messages (Without Crying)

I stared at the terminal for twenty minutes. The error message was ten lines long, referenced some file in the Nix store with a hash I'd never remember, and told me something about "infinite recursion" or "attribute missing" or... something. I don't even remember anymore. I just remember the feeling: Nix was speaking a language I didn't understand, and I was failing.

That was my first week with Nix. It took months before I could read an error message without my brain basically shutting down. But once you learn the pattern, once you understand what Nix is actually trying to tell you, the errors go from cryptic nonsense to useful hints.

Here's the thing nobody told me: Nix errors have a grammar. They follow a structure. Once you know how to parse them, they stop feeling like punishment and start feeling like maps. Pretty weird, but true.

The Anatomy of a Nix Error

Most Nix errors have four distinct layers. Let's look at a real one I hit last week:

error: infinite recursion encountered
       at /nix/var/nix/profiles/per-user/root/channels/nixpkgs/lib/attrsets.nix:123:5
          122|   recursiveUpdate = x: y:
          123|     mapAttrs (name: val:
             |     ^
          124|       if builtins.isAttrs val && builtins.isAttrs x.${name} or null
       … while evaluating a nested attribute set
       … while calling the 'map' builtin
       … in the expression at /home/user/flake.nix:42:8
Enter fullscreen mode Exit fullscreen mode

Looks terrifying, right? But it's actually telling you four specific things. Here's the breakdown:

Error type (the first line): infinite recursion encountered. This is what went wrong, not necessarily where, but the class of problem.

Location (the second line): The file and line number where Nix crashed. Usually deep in nixpkgs. This is rarely where you need to fix anything.

Trace (the "… while" lines): The evaluation stack, showing the chain of function calls. Reading these bottom to top shows the path from your code to the crash site.

Your code (the last "… in" line): The last trace entry is almost always the closest to where you introduced the problem. This is where you start debugging.

The rule I learned: The error location at the top is where Nix crashed. Your bug is almost always in the last line of the trace, or just above it. Read the trace bottom-up. That's it. That's the whole trick.

The Five Error Classes You'll Actually See

Nix errors basically group into a handful of recurring types. Recognizing them on sight saves you most of the diagnostic work.

1. Infinite Recursion Encountered

This means Nix tried to evaluate an attribute that depends on itself. Common causes: using self or super incorrectly in an overlay, or a callPackage where an attribute refers back to the derivation being defined.

# Classic trap: referring to 'pkgs' before it's fully constructed
let
  pkgs = import <nixpkgs> { overlays = [ overlay ]; };
  overlay = final: prev: {
    myPkg = pkgs.stdenv.mkDerivation { ... }; # ← should be 'final.stdenv'
  };
in pkgs
Enter fullscreen mode Exit fullscreen mode

Debug strategy: Add --show-trace to your nix build or nix eval call. The trace will point you to exactly which attribute triggered the cycle. Yeah, it's verbose, but it's worth it.

2. Value Is Not a Function / Not an Attribute Set

Nix's type system is lazy. Errors only surface during evaluation. This means you passed something of the wrong shape. Usually a missing argument, a typo in an attribute name, or forgetting to apply a function.

# Often caused by:
let
  f = { foo }: foo + 1;
in
f 42  # passing an int to a function expecting an attrset
Enter fullscreen mode Exit fullscreen mode

When the error says "value is not an attribute set" and points into nixpkgs, it usually means you've passed a derivation where a set of options was expected, or vice versa. For instance, calling pkgs.python3.withPackages and forgetting the lambda. I did this one twice last month.

3. Attribute Not Found

The most common error for nixpkgs newcomers. You're trying to access an attribute that doesn't exist on a set. Check spelling, check whether the package is in the right channel, and make sure you haven't confused a package set (like pkgs.python3Packages) with a package (pkgs.python3). Seriously, this trips up everyone at first.

error: attribute 'pythonPackages' missing

# The attribute set 'pkgs' does have this path, just named differently:
# pkgs.python3Packages.requests   ← correct
# pkgs.pythonPackages.requests    ← does not exist
Enter fullscreen mode Exit fullscreen mode

4. Builder Failed with Exit Code N

This is a build-time error, not an evaluation error. Nix successfully evaluated your derivation, but the build script failed. The error message itself is almost never useful. The real information is in the build log.

error: builder for '/nix/store/...-my-package-1.0.drv' failed with exit code 1
       For full logs, run:
         nix log /nix/store/...-my-package-1.0.drv
Enter fullscreen mode Exit fullscreen mode

Critical: Always run nix log immediately. The exit code alone tells you basically nothing. The log tells you everything: missing headers, failed tests, network errors, wrong phase outputs. Seriously, always check the log first.

5. Hash Mismatch

A fetchurl, fetchgit, or similar fetcher got content that doesn't match the declared hash. This is either a stale hash in your derivation, or the upstream source changed in place (which happens more than you'd hope).

error: hash mismatch in fixed-output derivation '/nix/store/...':
         specified: sha256-AAAA...
         got:       sha256-BBBB...
Enter fullscreen mode Exit fullscreen mode

Fix it by updating the hash. The "got" value is the correct one. Copy it into your derivation. For fetchgit, also update the rev if the source moved. Yeah, it's annoying when upstream changes things, but that's the price of reproducibility.

Reading the Trace Strategically

The "… while evaluating" lines form a stack trace in reverse evaluation order. Your instinct is to look at the top. Resist it. The top is the crash site, deep in nixpkgs or a library. The bottom is where your code called into the machinery.

"The trace is a telescope pointed backwards. The furthest point is where things broke. Your eye should start at the lens."

The mental model: Each "… while" is a stack frame. Nix was evaluating the bottom frame first: your flake.nix, your module, your overlay. It called into something, which called into something else, until finally it reached the frame that caused the failure.

My process:

  1. Find the last "… in the expression" line. This is typically pointing at your code. Open that file and go to that line.

  2. Read one frame up. The next "… while" line tells you what Nix was doing with your value: calling a function, iterating a list, constructing an attribute set.

  3. Match the operation to the error type. If it says "while evaluating a nested attribute set" and the error is "infinite recursion", look for circular attribute references at your entry point.

  4. Ignore frames that are entirely inside nixpkgs unless you're writing an overlay or patch. Those frames are symptoms, not causes.

  5. When unsure, add builtins.trace probes. Drop builtins.trace "reached here: ${builtins.typeOf x}" x near your suspect expression to confirm what's being evaluated and in what shape.

Practical Debugging Commands

Knowing the theory is one thing. These are the commands you'll actually reach for when things break:

# Always add --show-trace for eval errors
nix build .#myPackage --show-trace

# Evaluate a single attribute without building
nix eval .#packages.x86_64-linux.myPackage.name

# Enter a nix repl and poke at the value directly
nix repl
:lf .                    # load current flake
packages.x86_64-linux.myPackage.buildInputs

# Read the full build log after a builder failure
nix log /nix/store/HASH-myPackage-1.0.drv

# Inspect what a derivation will build without building it
nix derivation show .#myPackage
Enter fullscreen mode Exit fullscreen mode

Leveling Up: A Better REPL

The default nix repl is barebones. After months of typing :lf . and manually navigating attribute sets, I built a custom repl.nix that preloads all my hosts, configs, and helper functions. It looks like this:

# filename: repl.nix
{
  # Pass your host name when starting the repl
  host ? "xenomorph",
  ...
}:
let
  flake = builtins.getFlake (toString ./.);
  inherit (flake.inputs.nixpkgs) lib;
in
{
  inherit lib;
  inherit (flake) inputs;

  # Quick access to current host
  c = flake.nixosConfigurations.${host}.config;
  config = c;
  co = c.custom;
  pkgs = flake.nixosConfigurations.${host}.pkgs;

  # Helper functions for debugging
  keys = lib.attrNames;                    # List all attributes in a set
  deps = pkg: map (p: p.name or "unknown") (pkg.buildInputs ++ pkg.nativeBuildInputs or []);

  # Find where an option is defined (filters out nixos internals)
  where = path: let
    opt = lib.attrByPath (lib.splitString "." path) null
          flake.nixosConfigurations.${host}.options;
    decls = if opt != null && opt ? files then opt.files else [];
    isMine = f: !(lib.hasInfix "nixos/modules/" (toString f));
    in lib.filter isMine decls;

  # Reload without exiting repl
  reload = import ./repl.nix { inherit host; };
}
// flake.nixosConfigurations  # Merge in all host configs
Enter fullscreen mode Exit fullscreen mode

Why this is better:

  • keys pkgs: Instead of typing :p pkgs and scrolling through 50,000 attributes, get a clean list
  • where "services.nginx.virtualHosts": Find exactly which file defines that option, filtered to ignore nixpkgs internals
  • deps pkgs.hello: See build inputs for any package without digging through nixpkgs source
  • c / config / co: Short aliases for the current host's config and custom options
  • reload: Re-import the repl.nix without exiting (great when you're editing the repl itself)

Using it with a helper script:

#!/usr/bin/env bash
if [[ -f repl.nix ]]; then
  nix repl --arg host '"xenomorph"' --file ./repl.nix "$@"
else
  # Fallback to regular repl if not in a project with repl.nix
  nix repl .
fi
Enter fullscreen mode Exit fullscreen mode

Now when I hit an error, I can jump into the repl and inspect values immediately:

nix-repl> c.services.nginx.enable
true

nix-repl> where "services.nginx.virtualHosts"
[ /home/k1ng/nix/dotfiles.nix/modules/web/nginx.nix ]

nix-repl> keys co.persist.home
directories files

nix-repl> deps pkgs.neovim
[ "libuv" "msgpack" "tree-sitter" ... ]
Enter fullscreen mode Exit fullscreen mode

The inspiration for this came from Brian McGee's post about Nix's slow feedback loop. The REPL becomes a real debugging tool, not just a calculator.

Pro tip: The nix repl is criminally underused. You can load any flake, inspect attribute values, call functions, and test expressions interactively, all without triggering a full build. I use it basically every day now.

Quick Reference

Error What It Means First Thing to Try
infinite recursion Circular attribute dependency Check overlays and self/super usage
not a function Wrong type passed Check arg shapes with nix repl
attribute missing Typo or wrong channel Run nix search nixpkgs <name>
builder failed Build-time error Always read the full log with nix log
hash mismatch Update the hash Copy the "got" value into your derivation
Reading traces Read bottom-up Your entry point is at the end, not the top

Nix errors reward patience and a systematic reading strategy. The wall-of-red sensation disappears as soon as you know where to look first. That's almost always the bottom of the trace, not the top.

Once that clicks, debugging Nix feels less like archaeology and more like following a well-marked trail. Still annoying sometimes, honestly, but at least you know where you're going.

Resources

If you're stuck on a Nix error, these resources actually help:

Top comments (0)