fix: skip unreachable reparse points in detect_rooms_from_folders (#558)
On Windows, projects containing git-submodule junctions or dev-drive reparse points cause iterdir() to list the entry successfully but Path.is_dir() to raise OSError when it calls stat() internally. Reproducer: any Windows project with a submodule checked out as a junction (e.g. skills/pr-perfect) crashes mempalace init with: OSError: [WinError 448] The path cannot be traversed because it contains an untrusted mount point Fix: wrap every is_dir() call in detect_rooms_from_folders with try/except OSError so the scanner skips inaccessible entries and continues rather than aborting. Covers both the top-level pass and the one-level-deep nested pass. Two new tests mock the OSError on specific paths and verify the function returns correct rooms from the remaining accessible entries.
This commit is contained in:
committed by
GitHub
parent
1056018b52
commit
9c4b7302cc
@@ -9,12 +9,15 @@ Two ways to define rooms without calling any AI:
|
||||
No internet. No API key. Your files stay on your machine.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import yaml
|
||||
from pathlib import Path
|
||||
from collections import defaultdict
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Common room patterns — detected from folder names and filenames
|
||||
# Format: {folder_keyword: room_name}
|
||||
FOLDER_ROOM_MAP = {
|
||||
@@ -118,7 +121,12 @@ def detect_rooms_from_folders(project_dir: str) -> list:
|
||||
|
||||
# Check top-level directories first (most reliable signal)
|
||||
for item in project_path.iterdir():
|
||||
if item.is_dir() and item.name not in SKIP_DIRS:
|
||||
try:
|
||||
is_dir = item.is_dir() # WinError 448 — reparse point / untrusted mount point
|
||||
except OSError as exc:
|
||||
logger.debug("Skipping %s: %s", item, exc)
|
||||
continue
|
||||
if is_dir and item.name not in SKIP_DIRS:
|
||||
name_lower = item.name.lower().replace("-", "_")
|
||||
if name_lower in FOLDER_ROOM_MAP:
|
||||
room_name = FOLDER_ROOM_MAP[name_lower]
|
||||
@@ -132,9 +140,28 @@ def detect_rooms_from_folders(project_dir: str) -> list:
|
||||
|
||||
# Walk one level deeper for nested patterns
|
||||
for item in project_path.iterdir():
|
||||
if item.is_dir() and item.name not in SKIP_DIRS:
|
||||
for subitem in item.iterdir():
|
||||
if subitem.is_dir() and subitem.name not in SKIP_DIRS:
|
||||
try:
|
||||
item_is_dir = item.is_dir() # WinError 448 — reparse point / untrusted mount point
|
||||
except OSError as exc:
|
||||
logger.debug("Skipping %s: %s", item, exc)
|
||||
continue
|
||||
if item_is_dir and item.name not in SKIP_DIRS:
|
||||
try:
|
||||
subitems = list(
|
||||
item.iterdir()
|
||||
) # WinError 448 — iterdir can also fail on some reparse points
|
||||
except OSError as exc:
|
||||
logger.debug("Skipping contents of %s: %s", item, exc)
|
||||
continue
|
||||
for subitem in subitems:
|
||||
try:
|
||||
subitem_is_dir = (
|
||||
subitem.is_dir()
|
||||
) # WinError 448 — reparse point / untrusted mount point
|
||||
except OSError as exc:
|
||||
logger.debug("Skipping %s: %s", subitem, exc)
|
||||
continue
|
||||
if subitem_is_dir and subitem.name not in SKIP_DIRS:
|
||||
name_lower = subitem.name.lower().replace("-", "_")
|
||||
if name_lower in FOLDER_ROOM_MAP:
|
||||
room_name = FOLDER_ROOM_MAP[name_lower]
|
||||
|
||||
Reference in New Issue
Block a user