Claude Skill

nix-module-system

Imported from ypares/rigup.nix/riglets/nix-module-system.

LLM Mart · 0 points · 7 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download ypares-rigup.nix-riglets_nix-module-system-d48c9c3.zip · 4 KB
Part of ypares/rigup.nix — 4 skills

Install

skills CLI npx skills add https://github.com/YPares/rigup.nix/tree/main/riglets/nix-module-system
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install ypares-rigup-nix@llmmart
Git 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:

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 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
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.

No comments yet.

Reviews (0)

No reviews yet.

Related