.agents/skills/nix-platform-specific-options/SKILL.md
Write Nix modules with platform-specific options (NixOS vs Darwin) without infinite recursion. Use when mkIf causes evaluation errors or options don't exist across platforms.
npx skillsauth add edmundmiller/dotfiles nix-platform-specific-optionsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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.
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.
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 |
config = mkIf (!isDarwin) {
users.defaultUserShell = pkgs.zsh; # Darwin sees this path!
};
config = optionalAttrs (!isDarwin) {
users.defaultUserShell = pkgs.zsh; # Hidden from Darwin
};
# cfg.flavor evaluated at parse time → infinite recursion
(optionalAttrs (isDarwin && cfg.flavor == "personal") {
services.onepassword-secrets.enable = true;
})
# Platform check at parse time, config check lazy
(optionalAttrs isDarwin (mkIf (cfg.flavor == "personal") {
services.onepassword-secrets.enable = true;
}))
options.modules.foo = {
user = mkOpt types.str config.user.name; # Infinite recursion!
};
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
});
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;
}))
]);
| 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 |
When you see infinite recursion errors mentioning _module.freeformType or anon-43:
config. references in option defaultscfg. references in optionalAttrs conditionsmkIf (!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/
development
Read-only Linear issue access via the Linear GraphQL API.
data-ai
## <!-- Purpose: Teach agents fast day-to-day memory browse/search/read/sync workflows in pi-context-repo. --> name: searching-memory description: > Search, browse, and inspect memory quickly in pi-context-repo. Use when asked to find prior notes, inspect memory files, locate preferences, or sync recent memory updates. Trigger phrases: "search memory", "list memory files", "find in memory", "read memory file", "memory status", "sync memory". --- # Searching Memory Use this workflow for fast
development
Comprehensive guide for initializing or reorganizing agent memory into a deeply hierarchical file structure. Use when running /init, when user asks to set up memory, or when memory needs a major reorganization. Trigger phrases: "initialize memory", "set up memory", "populate memory", "build my memory", "memory init".
data-ai
Decomposes and reorganizes agent memory files into focused, single-purpose components. Use when memory has large multi-topic blocks, redundancy, or poor organization. Trigger phrases: "defrag memory", "reorganize memory", "clean up memory files", "split memory blocks".