I recently moved to the Noctalia shell, which can generate color themes for the apps I use (Doom Emacs, Alacritty, kitty, lazygit, tmux, yazi, zellij) and re-apply them whenever I change my wallpaper. I’m enabling the templates one app at a time. This post covers two of them, kitty and lazygit, and the three things that went wrong.
Setup: NixOS 26.05, Hyprland, Noctalia 5.4.1, and Home Manager as a NixOS module.
The idea: who owns the file?
Noctalia writes a generated theme file for each app, for example ~/.config/kitty/themes/noctalia.conf. The app still has to load it. On NixOS that is where it gets tricky: Home Manager usually generates an app’s main config as a read-only symlink into the Nix store, so Noctalia can’t edit it. If something else owns the file, either Nix has to load Noctalia’s theme itself, or the file has to be left unmanaged.
Most of my problems came down to two things trying to own the same file.
Problem 1: Kitty ignored the theme
I enabled the kitty and lazygit templates and changed the wallpaper. Kitty’s colors didn’t change, even after reloading with Ctrl+Shift+F5. I had already added the include to my kitty module:
programs.kitty = {
enable = true;
extraConfig = ''
include ${config.xdg.configHome}/kitty/themes/noctalia.conf
'';
};
I rebuilt and opened a new window. Still nothing, and the old teal colors were still there.
Debugging
- Look at the deployed file, not the source.
grep include ~/.config/kitty/kitty.confprinted nothing, so my Nix change had never reached the file. - Check that the symlink is current.
readlink -f ~/.config/kitty/kitty.confpointed at an old generation. - Read the whole rebuild output, not just the end. There was a failure that was easy to miss:
warning: the following units failed: home-manager-nekomangini.service
Existing file '.../lazygit/config.yml.bak' would be clobbered by backing up '.../lazygit/config.yml'
The system had activated, the secrets were decrypted and other services restarted. The command ended with a non-zero exit status (4), but the only hint was that one failed line. The Home Manager generation never activated, so none of my Home Manager changes were applied, including the kitty include.
The error is about lazygit, not kitty. Kitty was just the first place I noticed it.
Problem 2: Lazygit settings versus Noctalia’s theme
Noctalia’s lazygit template runs a hook that merges a gui.theme block into ~/.config/lazygit/config.yml. But as soon as I set lazygit options in Nix (like my Helix editor command), Home Manager generates that file as a read-only symlink and the hook can’t write into it.
The fix for problems 1 and 2
Both problems have the same root cause: Home Manager and Noctalia both wanted to own lazygit’s config.yml. The fix is to take Home Manager out of that file.
Step 1: clear the conflict. Move the stale backup away and remove the lazygit settings from Nix, so Home Manager stops generating config.yml:
mv ~/.config/lazygit/config.yml.bak ~/lazygit-config.yml.bak.old
programs.lazygit.enable = true; # no `settings` block
Rebuild and check that the output has no failed line:
sudo nixos-rebuild switch --flake .#sylphiette
tail -4 ~/.config/kitty/kitty.conf # should end with the include line
Then reload kitty with Ctrl+Shift+F5. This unblocked Home Manager, and the kitty theme appeared. I also commented out my old static kitty theme.nix. Kitty applies options in order and my extraConfig comes last, so the Noctalia include wins anyway, but hardcoded colors that never apply just cause confusion.
Step 2: keep my lazygit settings anyway. Removing settings meant my Helix editor command and other options had nowhere to live. Lazygit accepts a comma-separated list of config files in LG_CONFIG_FILE and merges them, so I split the config in two:
- my settings, generated by Nix into the store (read-only, reproducible)
- the theme, which Noctalia already writes as its own file,
themes/noctalia.yml
A small wrapper makes sure lazygit always loads both:
let
yaml = pkgs.formats.yaml { };
lazygitBase = yaml.generate "lazygit-base.yml" {
os = {
edit = "${pkgs.helix}/bin/hx {{filename}}";
editAtLine = "${pkgs.helix}/bin/hx {{filename}}:{{line}}";
editInTerminal = true;
};
gui = {
showFileTree = true;
nerdFontsVersion = "3";
};
};
lazygitWrapped = pkgs.symlinkJoin {
name = "lazygit-wrapped";
paths = [ pkgs.lazygit ];
nativeBuildInputs = [ pkgs.makeWrapper ];
postBuild = ''
wrapProgram $out/bin/lazygit \
--set LG_CONFIG_FILE "${lazygitBase},${config.xdg.configHome}/lazygit/themes/noctalia.yml"
'';
};
in
{
programs.lazygit = {
enable = true;
package = lazygitWrapped;
};
}
Because settings is still left out of programs.lazygit, Home Manager writes no config.yml and can no longer conflict with Noctalia.
Problem 3: It still opened vim
Pressing e in lazygit opened vim, not Helix. Everything I checked looked right:
git config core.editorwas Helix$EDITORand$VISUALwere Helix- the generated base file contained
hx
Debugging
I looked at the running process while vim was open:
ps -eo pid,ppid,args | grep -E 'vim|nvim|hx' | grep -v grep
The line showed fish -c vim -- "/path/to/file", which is lazygit’s own built-in default. So lazygit wasn’t using my settings at all. Then I checked what had started it, and what environment it had:
ps -o pid,ppid,args -p <parent-pid>
tr '\0' '\n' < /proc/<pid>/environ | grep LG_CONFIG_FILE
The process was /nix/store/...-lazygit-0.61.1/bin/lazygit, the plain, unwrapped package, and LG_CONFIG_FILE was missing. To make sure my config itself was fine, I ran lazygit with only my base file:
LG_CONFIG_FILE=/nix/store/...-lazygit-base.yml lazygit
That opened Helix. So the config was right, and something launched the wrong binary. Searching my repo for the culprit:
rg -n lazygit ~/nix-server -g '*.nix' -g '*.kdl' -g '*.lua' -g '*.fish'
turned up my fish alias:
lg = "${pkgs.lazygit}/bin/lazygit";
It pointed straight at the unwrapped package, so it skipped the wrapper and its environment variable.
The fix
In modules/home-manager/shell/fish/alias.nix, I pointed the alias at the wrapped package instead:
lg = "${config.programs.lazygit.package}/bin/lazygit";
This needs config in the file’s function arguments. Then I had to open a new terminal, because fish aliases are read at shell startup.
Takeaways
- Know who owns each file. If Home Manager generates it, Noctalia can’t edit it. Either load the generated theme file from your Nix config, or leave the file unmanaged.
- Verify the deployed result, not the source.
readlink -fandgrepon the real file tell you what is actually running. - Read the full rebuild output. A failed Home Manager service doesn’t stop the rest of the system from activating. The only clues are one
failedline and a non-zero exit status. - Look at the running process.
psand/proc/<pid>/environshowed exactly which binary was running and which environment variables it had. - Search for hardcoded store paths.
${pkgs.foo}/bin/fooin an alias or script silently bypasses any wrapper you build for that program.
Support my Journey
If you enjoyed this post and want to support my descent into Linux madness, you can find me here: