nix-module-system
Imported from ypares/rigup.nix/riglets/nix-module-system.
Install
npx skills add https://github.com/YPares/rigup.nix/tree/main/riglets/nix-module-system
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install ypares-rigup-nix@llmmart
git clone https://github.com/YPares/rigup.nix.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole ypares/rigup.nix collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
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
Files (rigup.nix)
-
references
-
troubleshooting.md 4.2 KB
# Nix Module System: Troubleshooting See also: [NixOS Wiki - Modules](https://wiki.nixos.org/wiki/NixOS_modules), [Import but don't import your NixOS modules](https://fzakaria.com/2024/07/29/import-but-don-t-import-your-nixos-modules) (2024) ## "The option X is defined multiple times" **Symptom:** ``` error: The option `services.foo.bar' is defined multiple times while it's expected to be unique. Definition values: - In `module-a.nix': "value1" - In `module-b.nix': "value2" ``` **Causes & Fixes:** 1. **Same module imported twice without deduplication** - If importing via paths: ensure same path string (not `./foo.nix` vs `../bar/foo.nix`) - If dynamic modules: add a `key` attribute for deduplication ```nix { key = "unique-id"; imports = [ actualModule ]; } ``` 2. **Intentionally setting same option in multiple places** - Use `lib.mkForce` to override: `lib.mkForce "winning-value"` - Use `lib.mkDefault` for fallback: `lib.mkDefault "fallback-value"` - Use `lib.mkMerge` to combine: `lib.mkMerge [ config1 config2 ]` 3. **Option type doesn't support merging** - Some types (`str`, `int`, `bool`) can't merge multiple definitions - List/attrset types usually can - Check option definition for `lib.types.X` ## "infinite recursion encountered" **Symptom:** ``` error: infinite recursion encountered ``` **Causes & Fixes:** 1. **Using `if` instead of `lib.mkIf`** ```nix # BAD: if evaluated immediately, might reference config being defined config = if config.foo.enable then { ... } else { }; # GOOD: mkIf deferred until after config assembled config = lib.mkIf config.foo.enable { ... }; ``` 2. **Circular option dependencies** ```nix # BAD: a depends on b, b depends on a config.a = config.b + 1; config.b = config.a + 1; ``` - Break the cycle by using `mkDefault` on one side - Or restructure to remove the dependency 3. **Accessing `config` in `specialArgs`** ```nix # BAD: config doesn't exist yet during specialArgs evaluation specialArgs = { foo = config.bar; }; # GOOD: use _module.args instead modules = [{ _module.args.foo = config.bar; }]; ``` ## "The option X does not exist" **Symptom:** ``` error: The option `foo.bar' does not exist. Definition values: - In `my-module.nix': "some value" ``` **Causes & Fixes:** 1. **Typo in option path** - Double-check spelling and nesting 2. **Missing module that declares the option** - Add the module that defines `options.foo.bar` to your imports 3. **Setting option for optional integration** - Use `_module.check = false` to allow undefined options - Or wrap in `lib.mkIf (builtins.hasAttr ...)` to conditionally set 4. **Using wrong options prefix** - NixOS options: `config.services.X` - Home-manager: `config.programs.X` or `config.home.X` - Custom modules: whatever you defined in `options` ## "cannot coerce X to a string" **Symptom:** ``` error: cannot coerce a set/list/function to a string ``` **Causes & Fixes:** 1. **Using attrset where string expected** ```nix # BAD: mkIf returns attrset, not string config.foo.bar = lib.mkIf condition "value"; # GOOD: mkIf wraps the whole assignment config = lib.mkIf condition { foo.bar = "value"; }; ``` 2. **Interpolating non-string in string** ```nix # BAD: attrset in string interpolation "prefix-${someAttrset}-suffix" # GOOD: convert to string first "prefix-${builtins.toJSON someAttrset}-suffix" ``` ## "attribute X missing" **Symptom:** ``` error: attribute 'foo' missing ``` **Causes & Fixes:** 1. **Optional module arg not provided** ```nix # If module expects { foo, ... }: but foo not in _module.args # Either add to _module.args/specialArgs, or use default: { foo ? defaultValue, ... }: ``` 2. **Accessing config before fully evaluated** - Move access to `config` section, not top-level `let` - Or use `lib.mkIf`/`lib.mkMerge` to defer ## Debugging Tips **See where an option is defined:** ```nix # In nix repl with nixos config loaded :p config.services.foo.bar.definitionsWithLocations ``` **Trace option evaluation:** ```nix # Add to module config.foo = lib.traceVal config.bar; # Prints bar's value during eval ``` **Check if option exists:** ```nix lib.hasAttrByPath [ "services" "foo" "enable" ] options ```
-
-
default.nix 1.1 KB · in bundle
-
SKILL.md 4.4 KB
# Nix Module System: Dark Corners Practical knowledge about `lib.evalModules` that's hard to find in official docs. **Sources:** - [nixpkgs/lib/modules.nix](https://github.com/NixOS/nixpkgs/blob/master/lib/modules.nix) — implementation - [Module system docs](https://github.com/NixOS/nixpkgs/blob/master/doc/module-system/module-system.chapter.md) — official chapter - [nix.dev deep dive](https://nix.dev/tutorials/module-system/deep-dive.html) — tutorial - [noogle.dev evalModules](https://noogle.dev/f/lib/modules/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 ```nix modules = [ ./foo.nix ./foo.nix ]; # Same path → evaluated once ``` **Function/attrset modules**: Deduplicated by `key` attribute ```nix # 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: ```nix 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: ```nix { _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: ```nix { _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: ```nix # 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: ```nix 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 # 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: ```nix { 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: ```nix { 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](references/troubleshooting.md) for detailed error explanations. **Quick fixes:** - "The option ... is defined multiple times" → Add `key` attribute or use `lib.mkForce`/`lib.mkMerge` - "infinite recursion encountered" → Use `lib.mkIf` instead of `if`, or check for circular dependencies - "The option ... does not exist" → Check spelling, or set `_module.check = false` for optional deps
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.