2026-05-14 Dotfiles Nix Audit Cleanup

What I set out to do

Asked Claude to audit my nix/HM config for non-standard or non-best-practice patterns. Wanted a survey, not a specific fix. Ended up working through the resulting findings in batches.

What I actually did

Seven commits, all on main:

  1. d0bdeda — Externalized the inline mmdc wrapper. It was using pkgs.writeShellScriptBin inline despite AGENTS.md §6 forbidding it. Migrated to pkgs.replaceVarsWith with an external template at nix/home-manager/files/scripts/mmdc.in. One snag: replaceVarsWith has a build-time safety check that regex-matches any @identifier@ in the output, including comments. Hit it because I wrote a literal @vars@ in a comment.
  2. 5e5caee — Collapsed 13 repeated home.file."bin/X" declarations into a single lib.mapAttrs' over a name→source attrset. null value means same name on both sides (10 of 13); explicit string for the 3 .py-stripping cases (check-upstream-issues.pycheck-upstream-issues etc.). -38 lines, byte-identical output verified by diff -r of the home-manager-files/bin/ derivations.
  3. 4819b85 — Three related PATH fixes bundled. The deep one: traced shell loading across four scenarios (Ghostty login, tmux pane, SSH-then-tmux, SSH-direct-to-tmux). Found that ssh box "tmux new -A -s main" gives the tmux server a broken PATH because only ~/.zshenv runs and zshenv didn’t prepend user paths. Also added a _dedupe_path precmd hook because typeset -U path wasn’t catching post-direnv additions (observable duplicate coreutils entry). Factored the PATH literal into a userPathSetup let-binding so the three init sites (zshenv, zprofile, zshrc) share one source of truth.
  4. 3e01b5f — Set xdg.enable = true (replaced four manual XDG_* exports — defaults match exactly), removed allowBroken = true (was doing nothing; nothing in closure is broken), updated the download-buffer-size comment to clarify it’s client-side only (daemon reads /etc/nix/nix.conf which we don’t manage).
  5. 2b73a43 — Deleted unused dotfiles registry entry (rg confirmed zero usages), deleted a dangling programs.docker-cli comment block, migrated zsh dotfiles to XDG via programs.zsh.dotDir = "${config.xdg.configHome}/zsh". HM auto-generates a stub ~/.zshenv that sets ZDOTDIR and chains.
  6. af8ce0a — Follow-up: the dotDir migration created untracked files in the repo root, since ~/.config/ IS the dotfiles repo root. Added zsh/ to .gitignore alongside other HM-managed dirs.
  7. d0d5cd5 — Docs only: clarified the two-flake layout in AGENTS.md (root flake.nix is dev-shell only, HM flake at nix/flake.nix), added a footgun note to the listFilesRecursive auto-import comment (renaming a module to lib.nix silently disables it).

What was striking

  • The SSH-direct-to-tmux gap was real and previously unnoticed. Tmux panes worked because .zshrc re-fixed PATH, masking the fact that the tmux server itself (and any run-shell it invokes) had a broken PATH. The fix is one line in envExtra. The reasoning to get there was the whole detective story: shell init order × macOS path_helper × tmux non-login config × romkatv’s keep-zshenv-minimal advice.
  • The replaceVarsWith @vars@ paranoia check treats unreplaced @identifier@ patterns as build failures. Cost me a minor comment rewrite but caught the kind of bug that would otherwise ship silently.
  • ~/.config/ being the repo root made the dotDir migration leak HM artifacts into the working tree until I gitignored. Non-obvious interaction between two unrelated choices (XDG paths + dotfiles repo location).
  • Three commits were pure DRY/cleanup wins with verified equivalent output — the mapAttrs' refactor, the PATH literal factoring, the xdg.enable swap. The diff -r comparison of the home-manager-files derivation between before/after was satisfying: different store hash because builder script differs, byte-identical contents.
  • Sometimes the right answer is “leave it alone.” I had flagged home.username as inline-instead-of-parameterized and the completionInit = "" workaround as worth rechecking, but on closer inspection: the first isn’t actionable without multi-host support, and HM #3965 is still open so the workaround is still needed. Knowing when a finding isn’t worth a commit is as important as the fix.

Top 3 tomorrow

  1. Maybe write the LiteLLM patches README — five accumulated patches in nix/home-manager/modules/patches/ deserve a status table (upstream PR vs permanent fork).
  2. Confirm no shell-startup regressions over a few days of using the new dotDir setup.
  3. Open home.username parameterization or revisit the completionInit workaround only if pain surfaces.