git clone https://github.com/YPares/rigup.nix
T=$(mktemp -d) && git clone --depth=1 https://github.com/YPares/rigup.nix "$T" && mkdir -p ~/.claude/skills && cp -r "$T/riglets/nix-module-system" ~/.claude/skills/ypares-rigup-nix-nix-module-system && rm -rf "$T"
riglets/nix-module-system/SKILL.mdNix Module System: Dark Corners
Practical knowledge about
lib.evalModules that's hard to find in official docs.
Sources:
- nixpkgs/lib/modules.nix — implementation
- Module system docs — official chapter
- nix.dev deep dive — tutorial
- noogle.dev evalModules — function reference
Module Identity & Deduplication
When the same module is included multiple times (e.g., via imports from different places),
evalModules deduplicates by identity:
Path-based modules: Deduplicated by path string
modules = [ ./foo.nix ./foo.nix ]; # Same path → evaluated once
Function/attrset modules: Deduplicated by
key attribute
# Without key: each inclusion is separate (can cause "defined multiple times" errors) modules = [ myModule myModule ]; # Evaluated twice! # With key: deduplicated myModule = { key = "my-unique-module-id"; imports = [ actualModule ]; }; modules = [ myModule myModule ]; # Evaluated once
Use
key when you wrap modules dynamically and need deduplication across import chains.
Module Arguments
_module.args
vs specialArgs
_module.argsspecialArgsBoth inject arguments into module functions, but differ in timing:
evalModules { specialArgs = { foo = "available during option declaration"; }; modules = [{ _module.args = { bar = "only available in config, not options"; }; }]; }
| | |
|---|---|---|
Available in | ✓ | ✗ |
Available in | ✓ | ✓ |
Can reference | ✗ | ✓ |
Rule of thumb: Use
specialArgs for things needed to declare options (like lib), use _module.args for runtime values (like pkgs).
_module.check
_module.checkDisable "unknown option" errors:
{ _module.check = false; }
Useful when modules set options that might not exist (e.g., optional integrations).
_module.freeformType
_module.freeformTypeAllow arbitrary attributes in config without declaring options:
{ _module.freeformType = lib.types.attrsOf lib.types.anything; # Now any attribute is allowed without explicit options whatever.you.want = "works"; }
Priority & Merging
mkDefault
/ mkForce
/ mkOverride
mkDefaultmkForcemkOverrideControl which definition wins when multiple modules set the same option:
# Priority scale: lower number wins lib.mkOverride 1000 "default priority" # Same as mkDefault lib.mkOverride 100 "normal priority" # Default when no mk* used lib.mkOverride 50 "force priority" # Same as mkForce # Shorthands lib.mkDefault x # mkOverride 1000 - easily overridden lib.mkForce x # mkOverride 50 - overrides most things
mkMerge
mkMergeCombine multiple config fragments:
config = lib.mkMerge [ { services.foo.enable = true; } (lib.mkIf condition { services.foo.port = 8080; }) ];
mkIf
(it's not just if
)
mkIfiflib.mkIf is not the same as Nix's if:
# Nix if: evaluated immediately, fails if option doesn't exist config = if condition then { foo = 1; } else { }; # lib.mkIf: deferred, only evaluated if condition is true config = lib.mkIf condition { foo = 1; };
mkIf prevents "infinite recursion" errors when the condition depends on other config values.
mkBefore
/ mkAfter
/ mkOrder
mkBeforemkAftermkOrderFor list-type options, control ordering:
{ environment.systemPackages = lib.mkBefore [ earlyPkg ]; # Prepend environment.systemPackages = lib.mkAfter [ latePkg ]; # Append environment.systemPackages = lib.mkOrder 500 [ midPkg ]; # Explicit order }
Disabling Modules
Remove a module from evaluation:
{ disabledModules = [ "services/web-servers/nginx.nix" # Path relative to modules root someImportedModule # Direct reference ]; }
Useful for replacing NixOS modules with custom implementations.
Common Errors & Fixes
See references/troubleshooting.md for detailed error explanations.
Quick fixes:
- "The option ... is defined multiple times" → Add
attribute or usekey
/lib.mkForcelib.mkMerge - "infinite recursion encountered" → Use
instead oflib.mkIf
, or check for circular dependenciesif - "The option ... does not exist" → Check spelling, or set
for optional deps_module.check = false