From 30bc8cb5d3b48d17d447098f6d0cd83c9f4e3c9b Mon Sep 17 00:00:00 2001 From: lassulus Date: Wed, 2 Jul 2025 16:50:21 +0200 Subject: [PATCH] flake: prevent outPath in multiselect to avoid serialization issues When using multiselect with outPath like {outPath,?meta}, nix evaluation collapses the attrset to just the outPath string, breaking further selection. Add validation during selector parsing to catch this and provide a clear error. --- pkgs/clan-cli/clan_lib/flake/flake.py | 10 ++++++++++ pkgs/clan-cli/clan_lib/flake/flake_test.py | 14 ++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/pkgs/clan-cli/clan_lib/flake/flake.py b/pkgs/clan-cli/clan_lib/flake/flake.py index ed3b33375..3a3b90310 100644 --- a/pkgs/clan-cli/clan_lib/flake/flake.py +++ b/pkgs/clan-cli/clan_lib/flake/flake.py @@ -168,6 +168,16 @@ def parse_selector(selector: str) -> list[Selector]: else: set_select_type = SetSelectorType.STR acc_selectors.append(SetSelector(type=set_select_type, value=acc_str)) + # Check for invalid multiselect patterns with outPath + for subselector in acc_selectors: + if subselector.value == "outPath": + msg = ( + "Cannot use 'outPath' in multiselect {...}. " + "When nix evaluates attrsets with outPath in a multiselect, " + "it collapses the entire attrset to just the outPath string, " + "breaking further selection. Use individual selectors instead." + ) + raise ValueError(msg) selectors.append(Selector(type=SelectorType.SET, value=acc_selectors)) submode = "" diff --git a/pkgs/clan-cli/clan_lib/flake/flake_test.py b/pkgs/clan-cli/clan_lib/flake/flake_test.py index d110b0a17..a6fa1269b 100644 --- a/pkgs/clan-cli/clan_lib/flake/flake_test.py +++ b/pkgs/clan-cli/clan_lib/flake/flake_test.py @@ -168,6 +168,20 @@ def test_out_path() -> None: assert test_cache.select(selectors) == "/nix/store/bla" +def test_out_path_in_multiselect_raises_exception() -> None: + with pytest.raises(ValueError, match="Cannot use 'outPath' in multiselect"): + parse_selector("{outPath}") + + with pytest.raises(ValueError, match="Cannot use 'outPath' in multiselect"): + parse_selector("x.{y,outPath}") + + with pytest.raises(ValueError, match="Cannot use 'outPath' in multiselect"): + parse_selector("a.b.{c,d,outPath,e}") + + with pytest.raises(ValueError, match="Cannot use 'outPath' in multiselect"): + parse_selector("{?outPath}") + + @pytest.mark.with_core def test_conditional_all_selector(flake: ClanFlake) -> None: m1 = flake.machines["machine1"]