From 3f811f52fd0a305fee02fbeef32e73ef8514ab30 Mon Sep 17 00:00:00 2001 From: Teknium Date: Sat, 21 Mar 2026 07:11:09 -0700 Subject: [PATCH] fix(toolsets): pass visited set by reference to prevent diamond dependency duplication Cherry-picked from PR #2292 by @Mibayy. Closes #2134. resolve_toolset() called visited.copy() per sibling include, breaking dedup for diamond dependencies (D resolved twice via B and C paths) and causing duplicate cycle warnings. Fix: pass visited directly so siblings share the same set. The .copy() for the all/* alias at the top level is kept so each top-level toolset gets an independent pass. Removes the print() cycle warning since hitting a visited name now usually means diamond (not a bug). --- toolsets.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/toolsets.py b/toolsets.py index 212b6ea22..23c8ba66a 100644 --- a/toolsets.py +++ b/toolsets.py @@ -355,24 +355,27 @@ def resolve_toolset(name: str, visited: Set[str] = None) -> List[str]: all_tools.update(resolved) return list(all_tools) - # Check for cycles + # Check for cycles / already-resolved (diamond deps). + # Silently return [] — either this is a diamond (not a bug, tools already + # collected via another path) or a genuine cycle (safe to skip). if name in visited: - print(f"⚠️ Circular dependency detected in toolset '{name}'") return [] - + visited.add(name) - + # Get toolset definition toolset = TOOLSETS.get(name) if not toolset: return [] - + # Collect direct tools tools = set(toolset.get("tools", [])) - - # Recursively resolve included toolsets + + # Recursively resolve included toolsets, sharing the visited set across + # sibling includes so diamond dependencies are only resolved once and + # cycle warnings don't fire multiple times for the same cycle. for included_name in toolset.get("includes", []): - included_tools = resolve_toolset(included_name, visited.copy()) + included_tools = resolve_toolset(included_name, visited) tools.update(included_tools) return list(tools)