Skip to content

Conversation

@Yo-sure
Copy link
Contributor

@Yo-sure Yo-sure commented Nov 22, 2025

PR Description

Problem

On Windows, FilesystemBackend returns paths with mixed separators (e.g., /\.circleci/) causing display issues in agent tools.

[AS-IS]

image image

[TO-BE]

image image

Why This Matters

DeepAgents provides essential filesystem capabilities for LangGraph-based agents, and cross-platform reliability is crucial for production deployments. Mixed path separators like /\ can negatively impact agent performance in two ways:

  1. User Experience: Inconsistent path displays reduce trust and usability, especially on Windows systems
  2. LLM Interpretation: Language models rely on clean, consistent path formats to make accurate decisions about file operations

This fix ensures POSIX-style paths across all platforms, improving both the developer experience and agent reliability.

Root Cause

There were two related but distinct issues affecting different modes:

Issue 1: virtual_mode=True - Logic Bug (Functional Failure)

String slicing logic in ls_info() and glob_info() used raw string concatenation to construct virtual paths:

# Problematic code
cwd_str = str(self.cwd)  # "C:\workspace"
if not cwd_str.endswith("/"):
    cwd_str += "/"  # "C:\workspace/" (mixed separators!)

abs_path = str(child_path)  # "C:\workspace\file.txt"
relative_path = abs_path[len(cwd_str):]  # Slicing fails - length mismatch!
virt_path = "/" + relative_path  # Result: "/\file.txt"

The string slicing failed because:

  1. cwd_str used backslashes: C:\workspace
  2. Adding / created mixed separators: C:\workspace/
  3. abs_path used different separators: C:\workspace\file.txt
  4. Length calculation was incorrect, producing: /\.circleci/

Issue 2: virtual_mode=False - Display Bug (UX Issue)

In non-virtual mode, paths were functionally correct but displayed with Windows backslashes:

abs_path = str(child_path)  # "C:\workspace\file.txt"
results.append({"path": abs_path, ...})  # Backslashes in output

This was purely a display inconsistency - functionality was correct, but output didn't match POSIX conventions.

Solution

For virtual_mode=True (Logic Fix):

Replace fragile string slicing with robust pathlib operations:

# Fixed code
try:
    # Use pathlib for robust cross-platform path handling
    relative_path = Path(abs_path).resolve().relative_to(self.cwd).as_posix()
except ValueError:
    # Path is outside cwd - normalize to POSIX for error reporting
    relative_path = abs_path.replace("\\", "/")

# Ensure virtual path starts with /
virt_path = "/" + relative_path if not relative_path.startswith("/") else relative_path

Key improvements:

  • Path.relative_to() correctly handles path differences
  • .as_posix() ensures forward slashes on all platforms
  • Security boundary (paths outside cwd) preserved for _resolve_path() validation

For virtual_mode=False (Display Fix):

Simple normalization for consistent output:

# Normalize path separators for cross-platform compatibility
normalized_path = abs_path.replace("\\", "/")
results.append({"path": normalized_path, ...})

Test Updates:

Updated test assertions to use platform-independent comparison:

# Before: assert str(f1) in paths
# After: assert str(f1).replace("\\", "/") in paths

Testing

  • ✅ All 6 backend unit tests pass on Windows 10
  • ✅ All 6 backend unit tests pass on Linux (WSL 2 Ubuntu 22.04)
  • ✅ Cross-platform compatibility verified: pathlib.Path.as_posix() works consistently across platforms
  • ✅ Security: Paths outside cwd are properly caught by _resolve_path() validation
  • ✅ Verified in production with langgraph dev on both Windows and WSL environments

Test Results:

image

Changes

  • libs/deepagents/deepagents/backends/filesystem.py: Updated ls_info() and glob_info() methods to use pathlib for path normalization
  • libs/deepagents/tests/unit_tests/backends/test_filesystem_backend.py: Updated test assertions for platform-independent path comparison

Relationship to Issue #427

This PR and Issue #427 address complementary Windows path handling problems in different layers:

This PR (Backend Layer - Output):

  • Scope: FilesystemBackend.ls_info() / glob_info() - path output formatting
  • Problem: Mixed separators (/\) in displayed paths
  • Impact: Display issues, UX degradation, potential LLM confusion
  • Solution: Normalize output to POSIX format

PR #454 / Issue #427 (Middleware Layer - Input):

Both fixes are necessary for robust Windows support:

  1. This PR (fix: normalize path separators in FilesystemBackend for cross-platform(OS:Window) compatibility #452) ensures clean path display (output normalization)
  2. PR fix(middleware): prevent silent corruption from Windows absolute paths (Issue #427) #454 prevents corrupt path input (input validation)

Together, they provide end-to-end Windows path handling in DeepAgents' filesystem layer.

…m compatibility

- Replace string slicing with Path.relative_to() for robust path handling
- Add fallback to maintain backward compatibility
- Normalize backslashes to forward slashes on Windows
- Update tests to use platform-independent path comparison

Fixes mixed path separators (e.g., /\.circleci/) on Windows in both
virtual_mode and normal_mode. Improves cross-platform consistency.
eyurtsev pushed a commit that referenced this pull request Nov 22, 2025
…s (Issue #427) (#454)

# PR Description - Issue #427

## Problem

On Windows, when users pass absolute paths (e.g.,
`F:\git\project\file.txt`) to filesystem tools, `_validate_path()`
creates invalid virtual paths by simply prefixing them with `/`,
resulting in malformed paths like `/F:/git/project/file.txt`.

**Impact: Silent Data Corruption**

The malformed path `/F:/git/file.txt` is not recognized as an absolute
path by Python's `pathlib` on Windows:
```python
Path('/F:/git/file.txt').is_absolute()  # False!
```

As a result, files are saved to unexpected locations (e.g.,
`{cwd}/F:/git/file.txt`) without any error message, causing silent data
corruption that users may not notice until much later.

## Root Cause

The `_validate_path()` function was designed to normalize all paths to
start with `/` for a virtual filesystem, but it didn't account for
Windows absolute paths with drive letters:

```python
# Original code (master branch)
def _validate_path(path: str, ...) -> str:
    normalized = os.path.normpath(path)
    normalized = normalized.replace("\\", "/")
    
    if not normalized.startswith("/"):
        normalized = f"/{normalized}"  # Blindly prefixes / to Windows paths!
    
    return normalized
```

**Why this fails on Windows:**

1. **Input**: `F:\git\project\file.txt`
2. **After `os.path.normpath()`**: `F:\git\project\file.txt` (unchanged
on Windows)
3. **After `.replace("\\", "/")`**: `F:/git/project/file.txt`
4. **Check**: `F:/git/...` doesn't start with `/` → adds prefix
5. **Output**: `/F:/git/project/file.txt` ❌

**Why Unix/Linux paths work fine:**

1. **Input**: `/home/user/file.txt`
2. **After normalization**: `/home/user/file.txt`
3. **Check**: Already starts with `/` → no prefix needed
4. **Output**: `/home/user/file.txt` ✅

The function inadvertently allows Unix absolute paths while breaking
Windows absolute paths.

## Solution

Add explicit validation to reject Windows absolute paths before
normalization:

```python
# Reject Windows absolute paths (e.g., C:\..., D:/...)
if re.match(r'^[a-zA-Z]:', path):
    msg = (
        f"Windows absolute paths are not supported: {path}. "
        f"Please use virtual paths starting with / (e.g., /workspace/file.txt)"
    )
    raise ValueError(msg)
```

This approach:
1. **Maintains consistency**: Enforces virtual filesystem paths across
all platforms
2. **Prevents ambiguity**: Avoids mixing Windows absolute paths with
virtual paths
3. **Provides clear feedback**: Error message guides users toward
correct usage
4. **Preserves security**: All existing path traversal checks remain
intact

## Testing

- ✅ All 9 new unit tests pass on **Windows 10**
- ✅ All tests pass on **Linux (WSL 2 Ubuntu 22.04)** - cross-platform
verified
- ✅ Validates rejection of Windows absolute paths (both `\` and `/`
separators)
- ✅ Confirms all existing functionality works (relative paths, path
traversal, allowed_prefixes)
- ✅ Unix/Linux absolute paths continue to work (backward compatible)
- ✅ No regressions in existing codebase

**Test Coverage:**
```python
def test_windows_absolute_path_rejected_backslash(self):
    """Test that Windows absolute paths with backslashes are rejected."""
    with pytest.raises(ValueError, match="Windows absolute paths are not supported"):
        _validate_path("C:\\Users\\Documents\\file.txt")

def test_windows_absolute_path_rejected_forward_slash(self):
    """Test that Windows absolute paths with forward slashes are rejected."""
    with pytest.raises(ValueError, match="Windows absolute paths are not supported"):
        _validate_path("C:/Users/Documents/file.txt")
```

## Changes

- `libs/deepagents/deepagents/middleware/filesystem.py`: 
  - Added `import re` for Windows drive letter detection
  - Added explicit Windows absolute path validation
  - Updated docstring with clear examples
- `libs/deepagents/tests/unit_tests/middleware/test_validate_path.py`:
  - New comprehensive test suite (9 test cases)
- Covers Windows paths, path traversal, normalization, and
allowed_prefixes

## Relationship to Other Work

This PR complements the path normalization work in `FilesystemBackend`
by addressing a different layer:

**This PR (Middleware - Input Validation):**
- Scope: `_validate_path()` in `middleware/filesystem.py`
- Purpose: Validate and reject invalid user inputs early
- Impact: Prevents silent corruption from Windows absolute paths

**PR #452 (Backend - Output Formatting):**
- Scope: `ls_info()` / `glob_info()` in `backends/filesystem.py`
- Purpose: Normalize path display for consistency
- Impact: Clean POSIX-style output across platforms
- Link: #452

**Design Philosophy:**

DeepAgents uses a virtual filesystem model where all paths should be:
- Virtual paths: `/workspace/file.txt`, `/memory/data.json`
(Recommended)
- Relative paths: `data/file.txt` (Converted to `/data/file.txt`)
- Unix absolute paths: `/home/user/file.txt` (Allowed for backward
compatibility)
- Windows absolute paths: `C:/Users/file.txt` (Rejected - incompatible
with virtual FS)

For Windows users, the recommended approach is to use WSL or virtual
paths as per the maintainer's guidance.

## Related Issues

Fixes #427
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant