From 690be1ea21b039e8d051978eb3ba1b995bc4f2d5 Mon Sep 17 00:00:00 2001 From: TomNicholas Date: Mon, 9 Mar 2026 15:21:10 -0400 Subject: [PATCH 1/2] Add regression test for list_dir corrupting directory names (#3753) ObjectStore.list_dir uses lstrip(prefix) which strips individual characters rather than a prefix string, corrupting child directory names when the prefix shares characters with them. Co-Authored-By: Claude Opus 4.6 --- tests/test_store/test_object.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/test_store/test_object.py b/tests/test_store/test_object.py index 3217069c2d..756f6309e3 100644 --- a/tests/test_store/test_object.py +++ b/tests/test_store/test_object.py @@ -86,6 +86,24 @@ async def test_store_getsize_prefix(self, store: ObjectStore) -> None: assert total_size == len(buf) * 2 +async def test_list_dir_with_nested_prefix(tmpdir): + """Regression test for https://github.com/zarr-developers/zarr-python/issues/3753. + + list_dir must not corrupt directory names when prefix shares characters + with child names, which happened because lstrip strips characters not prefixes. + """ + store = ObjectStore(LocalStore(prefix=str(tmpdir))) + + buf = cpu.Buffer.from_bytes(b"\x01") + await store.set("subdir/data.zarr/zarr.json", buf) + await store.set("subdir/data.zarr/temp/zarr.json", buf) + await store.set("subdir/data.zarr/temp/c/0/0", buf) + + results = sorted([item async for item in store.list_dir("subdir/data.zarr")]) + assert "temp" in results, f"Expected 'temp' in {results}" + assert "zarr.json" in results + + @pytest.mark.slow_hypothesis def test_zarr_hierarchy(): sync_store = ObjectStore(MemoryStore()) From 7fde50ad12bbd615ccc75204b100c468ad651f1f Mon Sep 17 00:00:00 2001 From: TomNicholas Date: Mon, 9 Mar 2026 15:23:39 -0400 Subject: [PATCH 2/2] Fix list_dir prefix stripping: use removeprefix instead of lstrip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit lstrip strips individual characters from the given set, not a prefix string. This corrupted directory names when the prefix shared characters with child names (e.g. "temp" → "emp" when prefix contained "t"). Fixes #3753 Co-Authored-By: Claude Opus 4.6 --- src/zarr/storage/_obstore.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/zarr/storage/_obstore.py b/src/zarr/storage/_obstore.py index 7ef0b40628..9568513d9b 100644 --- a/src/zarr/storage/_obstore.py +++ b/src/zarr/storage/_obstore.py @@ -260,7 +260,7 @@ async def _transform_list_dir( # We assume that the underlying object-store implementation correctly handles the # prefix, so we don't double-check that the returned results actually start with the # given prefix. - prefixes = [obj.lstrip(prefix).lstrip("/") for obj in list_result["common_prefixes"]] + prefixes = [obj.removeprefix(prefix).lstrip("/") for obj in list_result["common_prefixes"]] objects = [obj["path"].removeprefix(prefix).lstrip("/") for obj in list_result["objects"]] for item in prefixes + objects: yield item