From fe89d39c1bf031b440769382e93235b928d177b5 Mon Sep 17 00:00:00 2001 From: JSap0914 Date: Tue, 16 Jun 2026 12:35:23 +0900 Subject: [PATCH] Fix get_filename_list crash when a cached model folder is deleted cached_filename_list_ probes os.path.getmtime() for every directory recorded while the filename cache was built, including subfolders. If one of those folders is removed at runtime (e.g. the user deletes a model folder), getmtime() raises FileNotFoundError, which propagates out of get_filename_list() and breaks model listing instead of simply rebuilding the cache. Treat an inaccessible tracked folder as a stale-cache signal and return None so the list is rebuilt. --- folder_paths.py | 7 ++- .../cache_invalidation_test.py | 46 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 tests-unit/folder_paths_test/cache_invalidation_test.py diff --git a/folder_paths.py b/folder_paths.py index 7304e1b73..efa75756f 100644 --- a/folder_paths.py +++ b/folder_paths.py @@ -412,7 +412,12 @@ def cached_filename_list_(folder_name: str) -> tuple[list[str], dict[str, float] for x in out[1]: time_modified = out[1][x] folder = x - if os.path.getmtime(folder) != time_modified: + try: + if os.path.getmtime(folder) != time_modified: + return None + except OSError: + # A tracked folder was deleted or became inaccessible; treat the + # cache as stale so it gets rebuilt instead of raising. return None folders = folder_names_and_paths[folder_name] diff --git a/tests-unit/folder_paths_test/cache_invalidation_test.py b/tests-unit/folder_paths_test/cache_invalidation_test.py new file mode 100644 index 000000000..0d0514897 --- /dev/null +++ b/tests-unit/folder_paths_test/cache_invalidation_test.py @@ -0,0 +1,46 @@ +import os +import shutil +import tempfile + +import pytest + +import folder_paths + + +@pytest.fixture +def model_folder(): + """Register a temporary model category with a tracked subfolder.""" + folder_name = "cache_invalidation_test_cat" + with tempfile.TemporaryDirectory() as base: + category = os.path.join(base, "category") + subfolder = os.path.join(category, "sub") + os.makedirs(subfolder) + open(os.path.join(category, "a.safetensors"), "w").close() + open(os.path.join(subfolder, "b.safetensors"), "w").close() + + folder_paths.folder_names_and_paths[folder_name] = ( + [category], + {".safetensors"}, + ) + try: + yield folder_name, category, subfolder + finally: + folder_paths.folder_names_and_paths.pop(folder_name, None) + folder_paths.filename_list_cache.pop(folder_name, None) + folder_paths.cache_helper.clear() + + +def test_rebuilds_when_tracked_subfolder_deleted(model_folder): + folder_name, _category, subfolder = model_folder + + # Populate the filename cache, which records the mtime of every subfolder. + initial = folder_paths.get_filename_list(folder_name) + assert sorted(initial) == ["a.safetensors", "sub/b.safetensors"] + + # Remove a tracked subfolder at runtime (e.g. user deletes a model folder). + shutil.rmtree(subfolder) + + # The cache must be treated as stale and rebuilt, not crash with + # FileNotFoundError when probing the deleted folder's mtime. + refreshed = folder_paths.get_filename_list(folder_name) + assert refreshed == ["a.safetensors"]