From a81670be0ea24922512bfac6502ae66c0a30abba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?= Date: Sat, 21 Feb 2026 15:18:26 -0600 Subject: [PATCH 1/3] Add a failing test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Edgar Ramírez Mondragón --- tests/env/plugin/test_interface.py | 53 ++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/tests/env/plugin/test_interface.py b/tests/env/plugin/test_interface.py index 5c337d42e..37491ef03 100644 --- a/tests/env/plugin/test_interface.py +++ b/tests/env/plugin/test_interface.py @@ -3221,6 +3221,59 @@ def test_correct(self, hatch, temp_dir, isolated_data_dir, platform, global_appl assert members[1].project.location == member2_path assert members[2].project.location == member3_path + def test_member_outside_root_with_shared_prefix(self, temp_dir, isolated_data_dir, platform, global_application): + """Verify correct workspace member discovery with shared path prefix. + + os.path.commonprefix works character-by-character, so for paths that + share a partial directory name (e.g. 'local_app' and 'lib_member' both + start with 'l'), it would return an invalid path like '.../l' instead + of the true common ancestor directory. os.path.commonpath correctly + returns the nearest common directory, which is what we need as the + base for the member glob search. + + Example of the mismatch: + os.path.commonprefix(['/usr/lib', '/usr/local/lib']) == '/usr/l' + os.path.commonpath(['/usr/lib', '/usr/local/lib']) == '/usr' + """ + project_root = temp_dir / "local_app" + project_root.mkdir() + + member_path = temp_dir / "lib_member" + member_path.mkdir() + (member_path / "pyproject.toml").write_text( + """\ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "lib-member" +version = "0.1.0" +""" + ) + + config = { + "project": {"name": "my_app", "version": "0.0.1"}, + "tool": {"hatch": {"envs": {"default": {"workspace": {"members": [{"path": "../lib_member"}]}}}}}, + } + project = Project(project_root, config=config) + environment = MockEnvironment( + project_root, + project.metadata, + "default", + project.config.envs["default"], + {}, + isolated_data_dir, + isolated_data_dir, + platform, + 0, + global_application, + ) + + members = environment.workspace.members + assert len(members) == 1 + assert members[0].project.location == member_path + class TestWorkspaceDependencies: def test_basic(self, temp_dir, isolated_data_dir, platform, global_application): From 975fcb52fe600298f246b537a7af387fe75863be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?= Date: Sat, 21 Feb 2026 15:19:33 -0600 Subject: [PATCH 2/3] Fixes workspace member detection to properly handle shared path prefixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Edgar Ramírez Mondragón --- src/hatch/env/plugin/interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hatch/env/plugin/interface.py b/src/hatch/env/plugin/interface.py index 68a090064..f79839dd8 100644 --- a/src/hatch/env/plugin/interface.py +++ b/src/hatch/env/plugin/interface.py @@ -1238,7 +1238,7 @@ def members(self) -> list[WorkspaceMember]: path_spec = data["path"] normalized_path = os.path.normpath(os.path.join(root, path_spec)) absolute_path = os.path.abspath(normalized_path) - shared_prefix = os.path.commonprefix([root, absolute_path]) + shared_prefix = os.path.commonpath([root, absolute_path]) relative_path = os.path.relpath(absolute_path, shared_prefix) # Now we have the necessary information to perform an optimized glob search for members From 836f58061bf9bff17cfb42de4ee5e3fb671d8c51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?= Date: Sat, 21 Feb 2026 15:20:52 -0600 Subject: [PATCH 3/3] Add changelog entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Edgar Ramírez Mondragón --- docs/history/hatch.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/history/hatch.md b/docs/history/hatch.md index e22aabfda..9d2fc6a84 100644 --- a/docs/history/hatch.md +++ b/docs/history/hatch.md @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Fixes hatch shell type error for keep_env. - SBOM documentation for including SBOM files in `sdist` +- Fixes workspace member detection to properly handle shared path prefixes. ## [1.16.3](https://github.com/pypa/hatch/releases/tag/hatch-v1.16.3) - 2026-01-20 ## {: #hatch-v1.16.3 }