From 2ea3a3941680d56b2cb1b858bb3272a2b6b1e8f6 Mon Sep 17 00:00:00 2001 From: Mondo Diaz Date: Wed, 4 Feb 2026 13:19:03 -0600 Subject: [PATCH] fix: prevent false circular dependency detection on self-dependencies When packages like pytest have extras (e.g., pytest[testing]) that depend on the base package, the resolution was incorrectly detecting this as a circular dependency. Added additional check to skip dependencies that resolve to an artifact already in the visiting set, preventing the false cycle detection while still catching real circular dependencies. --- backend/app/dependencies.py | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/backend/app/dependencies.py b/backend/app/dependencies.py index 30463b9..c3c4b06 100644 --- a/backend/app/dependencies.py +++ b/backend/app/dependencies.py @@ -806,6 +806,10 @@ def resolve_dependencies( if dep_artifact_id == artifact_id: continue + # Skip if this artifact is already being visited (would cause cycle) + if dep_artifact_id in visiting: + continue + _resolve_recursive( dep_artifact_id, dep.dependency_project, @@ -1142,14 +1146,19 @@ async def resolve_dependencies_with_fetch( ).all() for dep in deps: - # Skip self-dependencies + # Skip self-dependencies (common with PyPI extras like pytest[testing] -> pytest) dep_proj_normalized = dep.dependency_project.lower() dep_pkg_normalized = _normalize_pypi_package_name(dep.dependency_package) curr_proj_normalized = proj_name.lower() curr_pkg_normalized = _normalize_pypi_package_name(pkg_name) if dep_proj_normalized == curr_proj_normalized and dep_pkg_normalized == curr_pkg_normalized: + logger.debug( + f"Skipping self-dependency: {pkg_key} -> {dep.dependency_project}/{dep.dependency_package}" + ) continue + # Also check if this dependency would resolve to the current artifact + # (handles cases where package names differ but resolve to same artifact) resolved_dep = _resolve_dependency_to_artifact( db, dep.dependency_project, @@ -1185,7 +1194,20 @@ async def resolve_dependencies_with_fetch( dep_artifact_id, dep_version, dep_size = resolved_dep + # Skip if resolved to same artifact (self-dependency at artifact level) if dep_artifact_id == artifact_id: + logger.debug( + f"Skipping self-dependency (same artifact): {pkg_key} -> " + f"{dep.dependency_project}/{dep.dependency_package} (artifact {dep_artifact_id[:12]})" + ) + continue + + # Skip if this artifact is already being visited (would cause cycle) + if dep_artifact_id in visiting: + logger.debug( + f"Skipping dependency already in resolution stack: {pkg_key} -> " + f"{dep.dependency_project}/{dep.dependency_package} (artifact {dep_artifact_id[:12]})" + ) continue await _resolve_recursive_async(