nix-platform-specific-options

Nix Module Platform-Specific Options

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "nix-platform-specific-options" with this command: npx skills add edmundmiller/dotfiles/edmundmiller-dotfiles-nix-platform-specific-options

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 Type Tool Evaluated

Platform (isDarwin , !isDarwin ) optionalAttrs

Parse time

Config values (cfg.enable , cfg.flavor ) mkIf

Lazy

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

Scenario Pattern

NixOS-only option optionalAttrs (!isDarwin) { ... }

Darwin-only option optionalAttrs isDarwin { ... }

Platform + enable check optionalAttrs isDarwin (mkIf cfg.enable { ... })

Platform + config value optionalAttrs isDarwin (mkIf (cfg.foo == "bar") { ... })

Option default from config Use null default, resolve in config section

Debugging

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

  • Search for config. references in option defaults

  • Search for cfg. references in optionalAttrs conditions

  • 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/

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

tmux-status-debug

No summary provided by upstream source.

Repository SourceNeeds Review
General

jj-history-investigation

No summary provided by upstream source.

Repository SourceNeeds Review
General

using-jj-workspaces

No summary provided by upstream source.

Repository SourceNeeds Review
General

agenix-secrets

No summary provided by upstream source.

Repository SourceNeeds Review