Nix module system
AI agents and Nix: parametrable skills/instructions and tools, packaged together in a reproducible and modular fashion
npx -y skills add YPares/rigup.nix --skill nix-module-systemAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
SKILL.md
4.4 KB, as published. Nobody here has run it
Nix 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
Both 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"; };
}];
}
specialArgs | _module.args | |
|---|---|---|
Available in options = { ... } | ✓ | ✗ |
Available in config = { ... } | ✓ | ✓ |
Can reference config | ✗ | ✓ |
Rule of thumb: Use specialArgs for things needed to declare options (like lib), use _module.args for runtime values (like pkgs).
_module.check
Disable "unknown option" errors:
{ _module.check = false; }
Useful when modules set options that might not exist (e.g., optional integrations).
_module.freeformType
Allow 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
Control 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
Combine multiple config fragments:
config = lib.mkMerge [
{ services.foo.enable = true; }
(lib.mkIf condition { services.foo.port = 8080; })
];
mkIf (it's not just if)
lib.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
For 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
keyattribute or uselib.mkForce/lib.mkMerge - "infinite recursion encountered" → Use
lib.mkIfinstead ofif, or check for circular dependencies - "The option ... does not exist" → Check spelling, or set
_module.check = falsefor optional deps