Skip to content

Commit e85ddd9

Browse files
committed
fix(filesystem): Prevents adding a redundant leading '/' to Windows absolute paths and normalize file paths to use POSIX separators
This ensures cross-platform consistency by enforcing POSIX-style path separators ('/') across the filesystem backend. Prevents adding a redundant leading '/' to Windows absolute paths (e.g., 'C:...') which previously caused failures when listing directory contents. Normalizes all incoming file paths within the FilesystemBackend to use '/' to avoid mixing '' and '/' separators.
1 parent 9ed6483 commit e85ddd9

File tree

2 files changed

+29
-11
lines changed

2 files changed

+29
-11
lines changed

libs/deepagents/backends/filesystem.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,8 @@ def ls_info(self, path: str) -> list[FileInfo]:
9595

9696
results: list[FileInfo] = []
9797

98-
# Convert cwd to string for comparison
99-
cwd_str = str(self.cwd)
98+
# Convert cwd to string for comparison (normalize to forward slashes)
99+
cwd_str = str(self.cwd).replace("\\", "/")
100100
if not cwd_str.endswith("/"):
101101
cwd_str += "/"
102102

@@ -110,6 +110,8 @@ def ls_info(self, path: str) -> list[FileInfo]:
110110
continue
111111

112112
abs_path = str(child_path)
113+
# Normalize to POSIX style (forward slashes only) for consistency
114+
abs_path = abs_path.replace("\\", "/")
113115

114116
if not self.virtual_mode:
115117
# Non-virtual mode: use absolute paths
@@ -140,16 +142,18 @@ def ls_info(self, path: str) -> list[FileInfo]:
140142
except OSError:
141143
results.append({"path": abs_path + "/", "is_dir": True})
142144
else:
143-
# Virtual mode: strip cwd prefix
145+
# Virtual mode: strip cwd prefix and normalize to POSIX
144146
if abs_path.startswith(cwd_str):
145147
relative_path = abs_path[len(cwd_str) :]
146-
elif abs_path.startswith(str(self.cwd)):
148+
elif abs_path.startswith(str(self.cwd).replace("\\", "/")):
147149
# Handle case where cwd doesn't end with /
148-
relative_path = abs_path[len(str(self.cwd)) :].lstrip("/")
150+
relative_path = abs_path[len(str(self.cwd).replace("\\", "/")) :].lstrip("/").lstrip("\\")
149151
else:
150152
# Path is outside cwd, return as-is or skip
151153
relative_path = abs_path
152154

155+
# Normalize to POSIX style (forward slashes only)
156+
relative_path = relative_path.replace("\\", "/")
153157
virt_path = "/" + relative_path
154158

155159
if is_file:
@@ -372,7 +376,10 @@ def _ripgrep_search(self, pattern: str, base_full: Path, include_glob: str | Non
372376
p = Path(ftext)
373377
if self.virtual_mode:
374378
try:
375-
virt = "/" + str(p.resolve().relative_to(self.cwd))
379+
rel_path = str(p.resolve().relative_to(self.cwd))
380+
# Normalize to POSIX style (forward slashes only)
381+
rel_path = rel_path.replace("\\", "/")
382+
virt = "/" + rel_path
376383
except Exception:
377384
continue
378385
else:
@@ -412,7 +419,10 @@ def _python_search(self, pattern: str, base_full: Path, include_glob: str | None
412419
if regex.search(line):
413420
if self.virtual_mode:
414421
try:
415-
virt_path = "/" + str(fp.resolve().relative_to(self.cwd))
422+
rel_path = str(fp.resolve().relative_to(self.cwd))
423+
# Normalize to POSIX style (forward slashes only)
424+
rel_path = rel_path.replace("\\", "/")
425+
virt_path = "/" + rel_path
416426
except Exception:
417427
continue
418428
else:
@@ -440,6 +450,9 @@ def glob_info(self, pattern: str, path: str = "/") -> list[FileInfo]:
440450
if not is_file:
441451
continue
442452
abs_path = str(matched_path)
453+
# Normalize to POSIX style (forward slashes only)
454+
abs_path = abs_path.replace("\\", "/")
455+
443456
if not self.virtual_mode:
444457
try:
445458
st = matched_path.stat()
@@ -454,15 +467,17 @@ def glob_info(self, pattern: str, path: str = "/") -> list[FileInfo]:
454467
except OSError:
455468
results.append({"path": abs_path, "is_dir": False})
456469
else:
457-
cwd_str = str(self.cwd)
470+
cwd_str = str(self.cwd).replace("\\", "/")
458471
if not cwd_str.endswith("/"):
459472
cwd_str += "/"
460473
if abs_path.startswith(cwd_str):
461474
relative_path = abs_path[len(cwd_str) :]
462-
elif abs_path.startswith(str(self.cwd)):
463-
relative_path = abs_path[len(str(self.cwd)) :].lstrip("/")
475+
elif abs_path.startswith(str(self.cwd).replace("\\", "/")):
476+
relative_path = abs_path[len(str(self.cwd).replace("\\", "/")) :].lstrip("/").lstrip("\\")
464477
else:
465478
relative_path = abs_path
479+
# Normalize to POSIX style (forward slashes only)
480+
relative_path = relative_path.replace("\\", "/")
466481
virt = "/" + relative_path
467482
try:
468483
st = matched_path.stat()

libs/deepagents/middleware/filesystem.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,10 @@ def _validate_path(path: str, *, allowed_prefixes: Sequence[str] | None = None)
119119
normalized = os.path.normpath(path)
120120
normalized = normalized.replace("\\", "/")
121121

122-
if not normalized.startswith("/"):
122+
# Only add leading slash if it's not a Windows absolute path (with drive letter or UNC prefix)
123+
# Windows absolute paths like "C:/Users/..." will be kept as-is
124+
drive, _ = os.path.splitdrive(normalized)
125+
if not normalized.startswith("/") and not drive:
123126
normalized = f"/{normalized}"
124127

125128
if allowed_prefixes is not None and not any(normalized.startswith(prefix) for prefix in allowed_prefixes):

0 commit comments

Comments
 (0)