NixOS Configuration Skill
Announce at start: "I'm using the nixos-config skill to work with your Nix configuration."
Critical Reminders
- Flakes only — never use channels or
nix-env
- Declarative always — never use imperative approaches when declarative solutions exist
- jj new before building — new files must be tracked; run
jj new so they appear in git HEAD
- Always build-test — never consider the task complete without a successful build
- Clean up result symlinks — remove any
result symlinks created during builds
Quick Reference
| Task |
Command |
| Rebuild NixOS (test) |
sudo nixos-rebuild test --flake .#<hostname> |
| Rebuild NixOS (switch) |
sudo nixos-rebuild switch --flake .#<hostname> |
| Home Manager build |
home-manager build --flake .#user@hostname --no-out-link |
| Home Manager switch |
home-manager switch --flake .#user@hostname |
| Check flake |
nix flake check |
| Evaluate expression |
nix-instantiate --eval -E '<expr>' |
| Interactive REPL |
nix repl |
| Show derivation |
nix show-derivation |
| Update single input |
nix flake update <input> |
| Update all inputs |
nix flake update |
Workflow
Phase 1: Analyze Existing Configuration
Before making changes, understand the current setup:
- Read
flake.nix to understand inputs, outputs, and structure
- Identify the module organization pattern (flat vs nested, custom namespaces like
mynix)
- Check for existing conventions:
- Option naming patterns
- Module enable/disable patterns
- Import structure
- Custom library functions
- Note the hostname(s) and user(s) configured
Phase 2: Plan Changes
Determine scope and approach:
- NixOS vs Home Manager — system-level services and kernel config go in NixOS modules; user programs, dotfiles, and user services go in Home Manager
- New module vs existing — prefer extending existing modules unless the feature is clearly separate
- Option design — plan option names, types, and defaults that match existing conventions
- Explain the planned changes to the user before implementing
Phase 3: Implement
Write the configuration following Nix best practices:
- Use
lib.mkEnableOption for boolean toggles
- Use
lib.mkOption with proper types (types.str, types.listOf, types.attrsOf, etc.)
- Use
lib.mkIf for conditional configuration
- Use
lib.mkDefault for defaults that users can override
- Use
lib.mkForce sparingly and only when necessary
- Use
lib.mkMerge to combine multiple config blocks
- Use
lib.assertMsg for meaningful error messages on invalid config
- Respect the project's existing conventions and namespaces
- Add description strings to custom options
Phase 4: Validate and Build
- If new files were created, run
jj new so they are tracked
- Build and test:
- NixOS:
sudo nixos-rebuild test --flake .#<hostname>
- Home Manager:
home-manager build --flake .#user@hostname --no-out-link
- Run
nix flake check if structural changes were made to the flake
- Clean up any
result symlinks: rm -f result
- Squash related changes if needed:
jj squash
Phase 5: Troubleshoot If Build Fails
If the build fails, follow this process:
- Parse the error message — identify the failing module, option, or derivation
- Use debugging tools:
nix-instantiate --eval to test expressions in isolation
nix repl to interactively explore values
nix show-derivation to inspect build dependencies
- Fix the issue and rebuild
- Repeat until the build succeeds
Error Handling
Common Failure Patterns
| Error |
Cause |
Fix |
error: getting status of '/nix/store/.../<file>' |
New file not tracked by git |
Run jj new to snapshot, then rebuild |
error: attribute 'X' missing |
Typo in option name or missing import |
Check spelling, verify module is imported in flake.nix |
error: infinite recursion encountered |
Circular dependency between options |
Break the cycle with lib.mkMerge or restructure |
error: value is a X while a Y was expected |
Wrong option type |
Check mkOption type declaration matches usage |
error: option 'X' does not exist |
Module not imported or option path wrong |
Verify import chain and option path |
collision between '/nix/store/...' |
Two packages provide same file |
Use lib.mkForce or packageOverrides to resolve |
error: flake 'X' does not provide attribute |
Wrong output path in flake |
Check nixosConfigurations / homeConfigurations names |
Build Fails After Adding New Files
This is the most common issue. New files are not visible to the Nix evaluator until they are tracked:
jj new # snapshot new files into git HEAD
# then rebuild
Remember to squash related changes afterward if needed.
Key Principles
- Declarative — every configuration choice should be expressed declaratively in Nix
- Minimal changes — make targeted changes that integrate with existing patterns
- Validate before done — never claim completion without a passing build
- Respect conventions — follow the project's established module structure, naming, and namespaces
- Explain changes — describe what was changed and why, highlight breaking changes or migration needs
Resources
Reference materials are available in the references/ directory:
nix-language.md — Nix language syntax and builtins
flake-patterns.md — Flake structure, inputs, and overlays
nixos-modules.md — Module structure, option declarations, and config patterns
home-manager.md — Home Manager modules, programs, and services
troubleshooting.md — Error catalog, debugging workflow, and migration patterns
1---2name: nixos-config3description: Use when working with NixOS configurations, Home Manager setups, Nix flakes, services, or troubleshooting Nix builds. Covers creating new configurations, modifying existing ones, adding packages, setting up services, writing modules, and debugging build failures.4---56# NixOS Configuration Skill78**Announce at start**: "I'm using the nixos-config skill to work with your Nix configuration."910## Critical Reminders1112- **Flakes only** — never use channels or `nix-env`13- **Declarative always** — never use imperative approaches when declarative solutions exist14- **jj new before building** — new files must be tracked; run `jj new` so they appear in git HEAD15- **Always build-test** — never consider the task complete without a successful build16- **Clean up result symlinks** — remove any `result` symlinks created during builds1718## Quick Reference1920| Task | Command |21|---|---|22| Rebuild NixOS (test) | `sudo nixos-rebuild test --flake .#<hostname>` |23| Rebuild NixOS (switch) | `sudo nixos-rebuild switch --flake .#<hostname>` |24| Home Manager build | `home-manager build --flake .#user@hostname --no-out-link` |25| Home Manager switch | `home-manager switch --flake .#user@hostname` |26| Check flake | `nix flake check` |27| Evaluate expression | `nix-instantiate --eval -E '<expr>'` |28| Interactive REPL | `nix repl` |29| Show derivation | `nix show-derivation` |30| Update single input | `nix flake update <input>` |31| Update all inputs | `nix flake update` |3233## Workflow3435### Phase 1: Analyze Existing Configuration3637Before making changes, understand the current setup:38391. Read `flake.nix` to understand inputs, outputs, and structure402. Identify the module organization pattern (flat vs nested, custom namespaces like `mynix`)413. Check for existing conventions:42 - Option naming patterns43 - Module enable/disable patterns44 - Import structure45 - Custom library functions464. Note the hostname(s) and user(s) configured4748### Phase 2: Plan Changes4950Determine scope and approach:51521. **NixOS vs Home Manager** — system-level services and kernel config go in NixOS modules; user programs, dotfiles, and user services go in Home Manager532. **New module vs existing** — prefer extending existing modules unless the feature is clearly separate543. **Option design** — plan option names, types, and defaults that match existing conventions554. Explain the planned changes to the user before implementing5657### Phase 3: Implement5859Write the configuration following Nix best practices:6061- Use `lib.mkEnableOption` for boolean toggles62- Use `lib.mkOption` with proper types (`types.str`, `types.listOf`, `types.attrsOf`, etc.)63- Use `lib.mkIf` for conditional configuration64- Use `lib.mkDefault` for defaults that users can override65- Use `lib.mkForce` sparingly and only when necessary66- Use `lib.mkMerge` to combine multiple config blocks67- Use `lib.assertMsg` for meaningful error messages on invalid config68- Respect the project's existing conventions and namespaces69- Add description strings to custom options7071### Phase 4: Validate and Build72731. If new files were created, run `jj new` so they are tracked742. Build and test:75 - **NixOS**: `sudo nixos-rebuild test --flake .#<hostname>`76 - **Home Manager**: `home-manager build --flake .#user@hostname --no-out-link`773. Run `nix flake check` if structural changes were made to the flake784. Clean up any `result` symlinks: `rm -f result`795. Squash related changes if needed: `jj squash`8081### Phase 5: Troubleshoot If Build Fails8283If the build fails, follow this process:84851. **Parse the error message** — identify the failing module, option, or derivation862. **Use debugging tools**:87 - `nix-instantiate --eval` to test expressions in isolation88 - `nix repl` to interactively explore values89 - `nix show-derivation` to inspect build dependencies903. **Fix the issue** and rebuild914. **Repeat** until the build succeeds9293## Error Handling9495### Common Failure Patterns9697| Error | Cause | Fix |98|---|---|---|99| `error: getting status of '/nix/store/.../<file>'` | New file not tracked by git | Run `jj new` to snapshot, then rebuild |100| `error: attribute 'X' missing` | Typo in option name or missing import | Check spelling, verify module is imported in `flake.nix` |101| `error: infinite recursion encountered` | Circular dependency between options | Break the cycle with `lib.mkMerge` or restructure |102| `error: value is a X while a Y was expected` | Wrong option type | Check `mkOption` type declaration matches usage |103| `error: option 'X' does not exist` | Module not imported or option path wrong | Verify import chain and option path |104| `collision between '/nix/store/...'` | Two packages provide same file | Use `lib.mkForce` or `packageOverrides` to resolve |105| `error: flake 'X' does not provide attribute` | Wrong output path in flake | Check `nixosConfigurations` / `homeConfigurations` names |106107### Build Fails After Adding New Files108109This is the most common issue. New files are not visible to the Nix evaluator until they are tracked:110111```bash112jj new # snapshot new files into git HEAD113# then rebuild114```115116Remember to squash related changes afterward if needed.117118## Key Principles1191201. **Declarative** — every configuration choice should be expressed declaratively in Nix1212. **Minimal changes** — make targeted changes that integrate with existing patterns1223. **Validate before done** — never claim completion without a passing build1234. **Respect conventions** — follow the project's established module structure, naming, and namespaces1245. **Explain changes** — describe what was changed and why, highlight breaking changes or migration needs125126## Resources127128Reference materials are available in the `references/` directory:129130- `nix-language.md` — Nix language syntax and builtins131- `flake-patterns.md` — Flake structure, inputs, and overlays132- `nixos-modules.md` — Module structure, option declarations, and config patterns133- `home-manager.md` — Home Manager modules, programs, and services134- `troubleshooting.md` — Error catalog, debugging workflow, and migration patterns