mcpField
N
nix-platform-specific-options

nix-platform-specific-options

github.com/edmundmillerDevelopment8044 installsUpdated today
Repository

About nix-platform-specific-options

nix-platform-specific-options is a reference skill for writing Nix modules with platform-dependent options. It explains how to use optionalAttrs for platform checks and mkIf for configuration-dependent conditions to avoid missing-option errors and infinite recursion.

Key features

  1. 01Separate platform and config checks
  2. 02Hide platform-specific option paths
  3. 03Avoid infinite recursion in module evaluation
  4. 04Use static option defaults and resolve them in config
  5. 05Debug common recursion patterns

Use cases

  1. 01Write NixOS- and Darwin-specific module options
  2. 02Troubleshoot missing-option errors across platforms
  3. 03Find config references that cause recursion

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/edmundmiller/dotfiles --skill nix-platform-specific-options

After installing, type/nix-platform-specific-optionsin Claude Code to confirm it loaded.

Details

From the project README

Nix Module Platform-Specific Options

Overview

When writing Nix modules that need to hide platform-specific options (NixOS vs Darwin), using mkIf alone causes infinite recursion. This skill documents the correct pattern.

The Problem

mkIf is evaluated lazily but the option path is still visible during module evaluation. This causes errors like:

error: The option `users.defaultUserShell' does not exist.

Or infinite recursion when config is referenced in option defaults or optionalAttrs conditions.

The Pattern

Use optionalAttrs for platform checks, mkIf for config-dependent checks.

Check TypeToolEvaluated
Platform (isDarwin, !isDarwin)optionalAttrsParse time
Config values (cfg.enable, cfg.flavor)mkIfLazy

Examples

❌ Wrong: mkIf for platform check
config = mkIf (!isDarwin) {
  users.defaultUserShell = pkgs.zsh;  # Darwin sees this path!
};
✅ Correct: optionalAttrs for platform check
config = optionalAttrs (!isDarwin) {
  users.defaultUserShell = pkgs.zsh;  # Hidden from Darwin
};
❌ Wrong: Config value in optionalAttrs condition
# cfg.flavor evaluated at parse time → infinite recursion
(optionalAttrs (isDarwin && cfg.flavor == "personal") {
  services.onepassword-secrets.enable = true;
})
✅ Correct: Nest mkIf inside optionalAttrs
# Platform check at parse time, config check lazy
(optionalAttrs isDarwin (mkIf (cfg.flavor == "personal") {
  services.onepassword-secrets.enable = true;
}))
❌ Wrong: config reference in option default
options.modules.foo = {
  user = mkOpt types.str config.user.name;  # Infinite recursion!
};
✅ Correct: Static default, use config in config section
options.modules.foo = {
  user = mkOpt types.str null;
};

config = mkIf cfg.enable (let
  user = if cfg.user != null then cfg.user else config.user.name;
in {
  # Use 'user' variable here
});

Combined Pattern

For modules with both platform-specific options AND config-dependent behavior:

config = mkIf cfg.enable (mkMerge [
  # Common config (all platforms)
  { /* ... */ }

  # Darwin-only options
  (optionalAttrs isDarwin {
    programs.zsh.interactiveShellInit = "...";
  })

  # NixOS-only options
  (optionalAttrs (!isDarwin) {
    users.defaultUserShell = pkgs.zsh;
  })

  # Darwin + config-dependent (nested)
  (optionalAttrs isDarwin (mkIf (cfg.flavor == "personal") {
    services.onepassword-secrets.enable = true;
  }))
]);

Quick Reference

ScenarioPattern
NixOS-only optionoptionalAttrs (!isDarwin) { ... }
Darwin-only optionoptionalAttrs isDarwin { ... }
Platform + enable checkoptionalAttrs isDarwin (mkIf cfg.enable { ... })
Platform + config valueoptionalAttrs isDarwin (mkIf (cfg.foo == "bar") { ... })
Option default from configUse null default, resolve in config section

Debugging

When you see infinite recursion errors mentioning _module.freeformType or anon-43:

  1. Search for config. references in option defaults
  2. Search for cfg. references in optionalAttrs conditions
  3. Search for mkIf (!isDarwin) or mkIf isDarwin guarding platform-specific options
# Find problematic patterns
grep -rn "mkOpt.*config\." modules/
grep -rn "optionalAttrs.*cfg\." modules/
grep -rn "mkIf.*isDarwin" modules/

Comments

Markdown supported