{"record":{"id":"42cc52a3eec0f0f5","repo":"unslothai/unsloth","slug":"linked-folder-is-no-longer-a-regular-directory","errorCode":null,"errorMessage":"Linked folder is no longer a regular directory","messagePattern":"Linked folder is no longer a regular directory","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"studio/backend/core/rag/folder_sync.py","lineNumber":171,"sourceCode":"    try:\n        if Path(normalized) == Path.home().resolve():\n            raise ValueError(\"The entire home folder cannot be registered\")\n    except RuntimeError:\n        pass\n    if contains_sensitive_path_component(normalized):\n        raise ValueError(\"Credential or configuration directories are not allowed\")\n    if is_denied_system_path(normalized):\n        raise ValueError(\"System directories are not allowed\")\n    return normalized\n\n\ndef _root_identity(root: str) -> tuple[int, int]:\n    try:\n        root_stat = os.lstat(root)\n    except OSError as exc:\n        raise RuntimeError(\"Linked folder is unavailable\") from exc\n    if stat.S_ISLNK(root_stat.st_mode) or not stat.S_ISDIR(root_stat.st_mode):\n        raise RuntimeError(\"Linked folder is no longer a regular directory\")\n    if os.path.normcase(os.path.realpath(root)) != os.path.normcase(root):\n        raise RuntimeError(\"Linked folder no longer resolves to its registered path\")\n    return root_stat.st_dev, root_stat.st_ino\n\n\ndef _store_identity(identity: tuple[int, int]) -> tuple[int | str, int | str]:\n    return tuple(value if value <= _SQLITE_INTEGER_MAX else f\"x{value:x}\" for value in identity)\n\n\ndef _file_identity(metadata: dict) -> tuple[int | str, int | str]:\n    return _store_identity((metadata[\"device\"], metadata[\"inode\"]))\n\n\ndef _load_identity(device_id: int | str, file_id: int | str) -> tuple[int, int]:\n    def load(value: int | str) -> int:\n        return (\n            int(value[1:], 16) if isinstance(value, str) and value.startswith(\"x\") else int(value)\n        )","sourceCodeStart":153,"sourceCodeEnd":189,"githubUrl":"https://github.com/unslothai/unsloth/blob/203007d19051dcd2ae33876786d117c99f6b0368/studio/backend/core/rag/folder_sync.py#L153-L189","documentation":"RuntimeError raised by _root_identity on a registered folder when lstat succeeds but the path is now a symlink or no longer a directory (e.g. someone replaced the folder with a symlink after registration). This is a TOCTOU defense: identity is keyed on (st_dev, st_ino) of a real directory, and a swapped-in symlink would make the recorded identity point somewhere else, so the sync refuses to proceed. Unlike initial validation (ValueError), this fires on persisted links during sync, indicating the folder changed out from under the registry.","triggerScenarios":"Deleting the registered folder and recreating the same name as a symlink (or as a file); a mount tool replacing the directory with a symlink to a cache; macOS Finder alias creation over the original folder.","commonSituations":"Users 'reorganizing' by symlinking the old path to a new location; dev environments where a script recreates directories as links; automation that swaps folder contents via symlink indirection.","solutions":["Remove the symlink and restore a real directory at the registered path, then re-sync.","If the location genuinely moved, unregister the old link and register the new path through validate_folder_path (which accepts the real directory).","Catch this RuntimeError in sync loops and quarantine the offending link rather than aborting all syncs."],"exampleFix":"# before\nrm -rf /data/docs && ln -s /mnt/big/docs /data/docs  # sync now fails\n\n# after\nrm /data/docs                      # remove the symlink\nunregister_folder('/data/docs')    # drop stale record\nregister_folder('/mnt/big/docs')   # register the real directory","handlingStrategy":"try-catch","validationCode":"import os, stat\n\ndef folder_still_real_directory(root: str) -> bool:\n    try:\n        st = os.lstat(root)\n    except OSError:\n        return False\n    return not stat.S_ISLNK(st.st_mode) and stat.S_ISDIR(st.st_mode)","typeGuard":null,"tryCatchPattern":"try:\n    sync(link)\nexcept RuntimeError as e:\n    if \"no longer a regular directory\" not in str(e):\n        raise\n    logger.warning(\"%s replaced with non-directory; re-register the real path\", link.path)\n    unregister(link)\n    notify_user_reregister(link.path)","preventionTips":["Re-check lstat identity (dev, ino) before each sync so swapped folders fail fast.","Forbid scripts/users from replacing registered folders with symlinks; alert on change.","When relocating data, always unregister the old link and register the new real path via validate_folder_path."],"tags":["filesystem","symlink","security","sync","toccou"],"backgroundTag":null,"analyzedSha":"203007d19051dcd2ae33876786d117c99f6b0368","analyzedAt":"2026-08-15T02:48:39.846Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}