Skip to content

Commit bb7dc6e

Browse files
Wauplinclaude
andauthored
Add HF_HUB_DISABLE_SYMLINKS env variable to force no-symlink cache (#4032)
* Add `HF_HUB_DISABLE_SYMLINKS` env variable to force no-symlink cache When a shared NAS is used as HF_HUB_CACHE across machines with different OSes, symlinks created on Linux cannot be followed by Windows clients. Setting `HF_HUB_DISABLE_SYMLINKS=1` makes `are_symlinks_supported()` always return False, so files are copied instead of symlinked. Closes #4022 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * add test + update markdown * extra careful --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 2593ff8 commit bb7dc6e

5 files changed

Lines changed: 22 additions & 1 deletion

File tree

docs/source/en/package_reference/environment_variables.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,13 @@ would need to explicitly pass `token=True` argument in your script.
148148
For time-consuming tasks, `huggingface_hub` displays a progress bar by default (using tqdm).
149149
You can disable all the progress bars at once by setting `HF_HUB_DISABLE_PROGRESS_BARS=1`.
150150

151+
### HF_HUB_DISABLE_SYMLINKS
152+
153+
If set, `huggingface_hub` will never create symlinks in the cache. Instead, files will be duplicated or moved directly into snapshot directories. This is a power-user feature making the cache directory run in a degraded mode where huge files ends-up duplicated on your hard-drive.
154+
155+
An example use case is when a shared network drive (e.g. NAS) is used as `HF_HUB_CACHE` across machines running different operating
156+
systems. Symlinks created on Linux are not always traversable on Windows, leading to errors. Setting `HF_HUB_DISABLE_SYMLINKS=1` avoids this problem at the cost of disk-space deduplication.
157+
151158
### HF_HUB_DISABLE_SYMLINKS_WARNING
152159

153160
If you are on a Windows machine, it is recommended to enable the developer mode or to run

src/huggingface_hub/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,9 @@ def list_files(repo_id: str):
229229
_is_true(__HF_HUB_DISABLE_PROGRESS_BARS) if __HF_HUB_DISABLE_PROGRESS_BARS is not None else None
230230
)
231231

232+
# Disable symlinks in the cache (files are copied instead of symlinked)
233+
HF_HUB_DISABLE_SYMLINKS: bool = _is_true(os.environ.get("HF_HUB_DISABLE_SYMLINKS"))
234+
232235
# Disable warning on machines that do not support symlinks (e.g. Windows non-developer)
233236
HF_HUB_DISABLE_SYMLINKS_WARNING: bool = _is_true(os.environ.get("HF_HUB_DISABLE_SYMLINKS_WARNING"))
234237

src/huggingface_hub/file_download.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ def are_symlinks_supported(cache_dir: str | Path | None = None) -> bool:
9494
cache_dir = constants.HF_HUB_CACHE
9595
cache_dir = str(Path(cache_dir).expanduser().resolve()) # make it unique
9696

97+
# If symlinks are explicitly disabled by the user, always return False
98+
if constants.HF_HUB_DISABLE_SYMLINKS:
99+
return False
100+
97101
# Check symlink compatibility only once (per cache directory) at first time use
98102
if cache_dir not in _are_symlinks_supported_in_dir:
99103
_are_symlinks_supported_in_dir[cache_dir] = True
@@ -640,7 +644,7 @@ def _create_symlink(src: str, dst: str, new_blob: bool = False) -> None:
640644
except ValueError:
641645
# Raised if src and dst are not on the same volume. Symlinks will still work on Linux/Macos.
642646
# See https://docs.python.org/3/library/os.path.html#os.path.commonpath
643-
_support_symlinks = os.name != "nt"
647+
_support_symlinks = os.name != "nt" and not constants.HF_HUB_DISABLE_SYMLINKS
644648
except PermissionError:
645649
# Permission error means src and dst are not in the same volume (e.g. destination path has been provided
646650
# by the user via `local_dir`. Let's test symlink support there)

src/huggingface_hub/utils/_runtime.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,7 @@ def dump_environment_info() -> dict[str, Any]:
427427
info["HF_HUB_OFFLINE"] = constants.HF_HUB_OFFLINE
428428
info["HF_HUB_DISABLE_TELEMETRY"] = constants.HF_HUB_DISABLE_TELEMETRY
429429
info["HF_HUB_DISABLE_PROGRESS_BARS"] = constants.HF_HUB_DISABLE_PROGRESS_BARS
430+
info["HF_HUB_DISABLE_SYMLINKS"] = constants.HF_HUB_DISABLE_SYMLINKS
430431
info["HF_HUB_DISABLE_SYMLINKS_WARNING"] = constants.HF_HUB_DISABLE_SYMLINKS_WARNING
431432
info["HF_HUB_DISABLE_EXPERIMENTAL_WARNING"] = constants.HF_HUB_DISABLE_EXPERIMENTAL_WARNING
432433
info["HF_HUB_DISABLE_IMPLICIT_TOKEN"] = constants.HF_HUB_DISABLE_IMPLICIT_TOKEN

tests/test_cache_no_symlinks.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ class TestCacheLayoutIfSymlinksNotSupported(unittest.TestCase):
2424
def test_are_symlinks_supported_default(self) -> None:
2525
self.assertTrue(are_symlinks_supported())
2626

27+
@patch("huggingface_hub.file_download.constants.HF_HUB_DISABLE_SYMLINKS", True)
28+
@patch("huggingface_hub.file_download._are_symlinks_supported_in_dir", {HF_HUB_CACHE: True})
29+
def test_are_symlinks_supported_disabled_by_env(self) -> None:
30+
"""Setting HF_HUB_DISABLE_SYMLINKS=1 forces are_symlinks_supported() to return False."""
31+
self.assertFalse(are_symlinks_supported())
32+
2733
@patch("huggingface_hub.file_download.os.symlink")
2834
@patch("huggingface_hub.file_download._are_symlinks_supported_in_dir", {})
2935
def test_are_symlinks_supported_windows_specific_dir(self, mock_symlink: Mock) -> None:

0 commit comments

Comments
 (0)