Summary
When several standalone user@host homes share one user aspect, all host-conditional configuration in that aspect resolves against only the first home's host binding (first in attrset order). The single resolution is then reused for every sibling home:
- Content keyed to the first host leaks into every other home.
- Content keyed to any later host is silently dropped everywhere — including in the home actually bound to that host.
All three host-conditioning mechanisms are affected identically: provides.<host>, den.lib.policy.when ({ host, ... }: ...), and a bare parametric include gated on host.name. Declared-host (den.hosts) resolution is not affected — provides.<host> on a user aspect routes to the right nixosConfigurations and never crosses hosts.
Reproduced on 1614f6f (fix #627) and current main 99cc0c5a1cc846cb1be681344b10d2731d430e13.
The existing dead-bug suite (templates/ci/modules/deadbugs/standalone-home-host-context.nix, added with #605) covers each pattern with a single home, which is why CI stays green.
Minimal reproduction
{
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/0bb7ec54c8483066ec9d7720e780a5caa71f8612";
flake-parts.url = "github:hercules-ci/flake-parts";
home-manager.url = "github:nix-community/home-manager";
home-manager.inputs.nixpkgs.follows = "nixpkgs";
den.url = "github:denful/den";
};
outputs = inputs: inputs.flake-parts.lib.mkFlake { inherit inputs; } (
{ lib, den, ... }:
{
systems = [ "x86_64-linux" ];
imports = [ inputs.den.flakeModule ];
den = {
default.homeManager.home.stateVersion = "25.11";
# A: host-named provides, two homes
homes.x86_64-linux."ben@alpha" = { };
homes.x86_64-linux."ben@beta" = { };
aspects.ben = {
homeManager.home = { username = "ben"; homeDirectory = "/home/ben"; };
provides.alpha.homeManager.programs.zsh.profileExtra = "alpha-only";
};
# D: policy.when gate
homes.x86_64-linux."carl@alpha" = { };
homes.x86_64-linux."carl@beta" = { };
aspects.carl = {
homeManager.home = { username = "carl"; homeDirectory = "/home/carl"; };
includes = [
(den.lib.policy.when ({ host, ... }: host.name == "alpha")
(_: den.lib.policy.include { homeManager.programs.zsh.profileExtra = "alpha-only"; }))
];
};
# E: parametric include
homes.x86_64-linux."dana@alpha" = { };
homes.x86_64-linux."dana@beta" = { };
aspects.dana = {
homeManager.home = { username = "dana"; homeDirectory = "/home/dana"; };
includes = [
({ host, ... }: lib.optionalAttrs (host.name == "alpha") {
homeManager.programs.zsh.profileExtra = "alpha-only";
})
];
};
# F: keys on both hosts — first-wins, not union
homes.x86_64-linux."fred@alpha" = { };
homes.x86_64-linux."fred@beta" = { };
aspects.fred = {
homeManager.home = { username = "fred"; homeDirectory = "/home/fred"; };
provides.alpha.homeManager.programs.zsh.profileExtra = "from-alpha";
provides.beta.homeManager.programs.zsh.envExtra = "from-beta";
};
# G: key on the second host only — dropped everywhere
homes.x86_64-linux."gina@alpha" = { };
homes.x86_64-linux."gina@beta" = { };
aspects.gina = {
homeManager.home = { username = "gina"; homeDirectory = "/home/gina"; };
provides.beta.homeManager.programs.zsh.profileExtra = "beta-only";
};
# C: declared hosts — NOT affected (control)
hosts.x86_64-linux.hosta.users.erin = { };
hosts.x86_64-linux.hostb.users.erin = { };
default.nixos.system.stateVersion = "25.11";
default.nixos.boot.loader.grub.enable = false;
aspects.erin = {
homeManager.home = { username = "erin"; homeDirectory = "/home/erin"; };
provides.hosta.nixos.networking.enableIPv6 = false;
};
};
}
);
}
Observed vs expected
eval (config.programs.zsh.profileExtra unless noted) |
observed |
expected |
A ben@alpha |
alpha-only |
alpha-only |
A ben@beta |
alpha-only |
"" |
D carl@alpha / E dana@alpha |
alpha-only |
alpha-only |
D carl@beta / E dana@beta |
alpha-only |
"" |
F fred@alpha |
from-alpha (envExtra "") |
same |
F fred@beta |
from-alpha, envExtra "" |
"" / from-beta |
G gina@alpha |
"" |
"" |
G gina@beta |
"" |
beta-only |
C hosta networking.enableIPv6 |
false |
false |
C hostb networking.enableIPv6 |
true |
true |
Case G is the sharpest form: the only home that should receive the config gets nothing, because the shared resolution was made against alpha's binding.
Context
Found while restructuring a fleet (one user across a NixOS host plus two user@host standalone homes) to use the documented mutual-provides paradigm ("Standalone HomeManager - Host Specific Configuration" in the mutual guide, and provides.<host> in the home-manager guide). With more than one home per user aspect, per-host home configuration is currently inexpressible; identical bundles delivered under several host keys still work only because the first host's key covers the rest by accident.
Summary
When several standalone
user@hosthomes share one user aspect, all host-conditional configuration in that aspect resolves against only the first home's host binding (first in attrset order). The single resolution is then reused for every sibling home:All three host-conditioning mechanisms are affected identically:
provides.<host>,den.lib.policy.when ({ host, ... }: ...), and a bare parametric include gated onhost.name. Declared-host (den.hosts) resolution is not affected —provides.<host>on a user aspect routes to the rightnixosConfigurationsand never crosses hosts.Reproduced on
1614f6f(fix #627) and current main99cc0c5a1cc846cb1be681344b10d2731d430e13.The existing dead-bug suite (
templates/ci/modules/deadbugs/standalone-home-host-context.nix, added with #605) covers each pattern with a single home, which is why CI stays green.Minimal reproduction
Observed vs expected
config.programs.zsh.profileExtraunless noted)ben@alphaalpha-onlyalpha-onlyben@betaalpha-only""carl@alpha/ Edana@alphaalpha-onlyalpha-onlycarl@beta/ Edana@betaalpha-only""fred@alphafrom-alpha(envExtra"")fred@betafrom-alpha, envExtra""""/from-betagina@alpha""""gina@beta""beta-onlyhostanetworking.enableIPv6falsefalsehostbnetworking.enableIPv6truetrueCase G is the sharpest form: the only home that should receive the config gets nothing, because the shared resolution was made against
alpha's binding.Context
Found while restructuring a fleet (one user across a NixOS host plus two
user@hoststandalone homes) to use the documented mutual-provides paradigm ("Standalone HomeManager - Host Specific Configuration" in the mutual guide, andprovides.<host>in the home-manager guide). With more than one home per user aspect, per-host home configuration is currently inexpressible; identical bundles delivered under several host keys still work only because the first host's key covers the rest by accident.