Skip to content

Path validation fails for WSL paths on Windows (MinGW) #18594

@yclian

Description

@yclian

What happened?

Gemini CLI fails to validate WSL paths on Windows when running in a MinGW environment. All file operations fail with "Path not in workspace" errors, even though the path is clearly within the workspace.

The core issue: When paths are normalized to lowercase on Windows, fs.realpathSync() fails to resolve WSL UNC paths (e.g., \\wsl.localhost\ubuntu\...) because the actual filesystem uses mixed case (e.g., \\wsl.localhost\Ubuntu\...). When realpathSync() fails, it returns a truncated path in the error object, which then fails validation.

For example:

 > list all MD files in this repo
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ x  FindFiles {"pattern":"**/*.md"}                                                                                                                                                │
│                                                                                                                                                                                   │
│ Path not in workspace: Attempted path "\\wsl.localhost\Ubuntu\home\yclian\Development\github.com\google-gemini\gemini-cli" resolves outside the allowed workspace directories:    │
│ \\wsl.localhost\Ubuntu\home\yclian\Development\github.com\google-gemini\gemini-cli, C:\Users\yclian\Development\github.com\obra\superpowers\skills\using-superpowers,             │
│ \\WSL.LOCALHOST\UBUNTU\home\yclian\Development\github.com\google-gemini\gemini-cli\.gemini\skills\docs-writer or the project temp directory:                                      │
│ C:\Users\yclian\.gemini\tmp\9fae3ef839388a51e8c3401e36b0aa881ab6b25d07eecdf9e027b5a5ba9248f2                                                                                      │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ x  Shell {"description":"List all Markdown files in the repository using the find command.","command":"find . -name \"*.md\""}                                                    │
│                                                                                                                                                                                   │
│ Path not in workspace: Attempted path "\\wsl.localhost\Ubuntu\home\yclian\Development\github.com\google-gemini\gemini-cli" resolves outside the allowed workspace directories:    │
│ \\wsl.localhost\Ubuntu\home\yclian\Development\github.com\google-gemini\gemini-cli, C:\Users\yclian\Development\github.com\obra\superpowers\skills\using-superpowers,             │
│ \\WSL.LOCALHOST\UBUNTU\home\yclian\Development\github.com\google-gemini\gemini-cli\.gemini\skills\docs-writer or the project temp directory:                                      │
│ C:\Users\yclian\.gemini\tmp\9fae3ef839388a51e8c3401e36b0aa881ab6b25d07eecdf9e027b5a5ba9248f2                                                                                      │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯

Root cause

The issue is in packages/core/src/utils/workspaceContext.ts, specifically in the isPathWithinRoot() method. On Windows, path.relative() performs case-sensitive string comparison for UNC/WSL paths, even though Windows filesystems are case-insensitive.

When comparing:

  • \\wsl.localhost\Ubuntu-20.04\... (original case from user input)
  • \\wsl.localhost\ubuntu-20.04\... (lowercase from stored workspace)

The path.relative() function treats these as different paths and returns ..\..\.., causing the validation to fail.

Breaking commits

This issue was likely introduced by these two commits that added lowercase path normalization on Windows:

  1. 5f569fa10 (2026-01-27) - "refactor(core): centralize path validation and allow temp dir access for tools (refactor(core): centralize path validation and allow temp dir access for tools #17185)"

    • Added toLowerCase() normalization in config.ts:1857
  2. 6fb3b0900 (2026-02-06) - "Shorten temp directory (Shorten temp directory #17901)"

    • Added normalizePath() method in projectRegistry.ts:70-76 that normalizes to lowercase

Both commits normalized paths to lowercase for case-insensitive comparison on Windows, but this breaks WSL paths because fs.realpathSync() requires exact filesystem casing to resolve UNC paths.

The issue involves a chain of problems:

  1. Premature lowercase normalization: Paths are normalized to lowercase (e.g., \\wsl.localhost\ubuntu\...) before filesystem operations
  2. Failed path resolution: fs.realpathSync() fails because WSL paths are case-sensitive for UNC paths - the actual path is \\wsl.localhost\Ubuntu\... (capital U)
  3. Path truncation: When realpathSync() fails with ENOENT, the error object's e.path contains a truncated path (e.g., .../development instead of .../development/github.com/google-gemini/gemini-cli)
  4. Validation failure: The truncated path is then compared against the full workspace path, causing path.relative() to return ..\..\.., which fails the "within workspace" check

Proposed fix

Note: My current workaround uses Option 2 (defensive fixes in workspaceContext.ts) applied to my local installation. I could submit a PR with Option 1 (the proper fix that removes the premature normalization) later this week.

Option 1: Proper fix

Remove the premature lowercase normalization that causes the issue:

1. Fix config.ts (line ~1857)

Remove the lowercase normalization:

const realpath = (p: string) => {
  let resolved: string;
  try {
    resolved = fs.realpathSync(p);
  } catch {
    resolved = path.resolve(p);
  }
  // Don't normalize to lowercase - it breaks WSL paths
  return resolved;
};

2. Fix projectRegistry.ts (line ~70-76)

Remove the lowercase normalization:

private normalizePath(projectPath: string): string {
  // Don't normalize to lowercase on Windows - it breaks WSL paths
  return path.resolve(projectPath);
}

3. Fix isPathWithinRoot in workspaceContext.ts (line ~207)

Add case-insensitive comparison for Windows:

private isPathWithinRoot(
  pathToCheck: string,
  rootDirectory: string,
): boolean {
  // Normalize both paths to lowercase on Windows for case-insensitive comparison
  const normalizedRoot = os.platform() === 'win32'
    ? rootDirectory.toLowerCase()
    : rootDirectory;
  const normalizedPath = os.platform() === 'win32'
    ? pathToCheck.toLowerCase()
    : pathToCheck;

  const relative = path.relative(normalizedRoot, normalizedPath);
  return (
    !relative.startsWith(`..${path.sep}`) &&
    relative !== '..' &&
    !path.isAbsolute(relative)
  );
}

Important: Normalize to lowercase only for comparison, never for storage or filesystem operations.

Option 2: Defensive fix / Local workaround

This defensive workaround in workspaceContext.ts handles incorrect casing:

1. Fix Constructor (line ~26)

 constructor(targetDir: string, additionalDirectories: string[] = []) {
-  this.targetDir = targetDir;
+  // Ensure targetDir is in original case from filesystem
+  try {
+    this.targetDir = fs.realpathSync(targetDir);
+  } catch {
+    this.targetDir = targetDir;
+  }
   this.addDirectory(targetDir);
   this.addDirectories(additionalDirectories);
   this.initialDirectories = new Set(this.directories);
 }

2. Fix fullyResolvedPath (line ~182)

 private fullyResolvedPath(pathToCheck: string): string {
   try {
-    return fs.realpathSync(path.resolve(this.targetDir, pathToCheck));
+    let resolvedInput = path.resolve(this.targetDir, pathToCheck);
+
+    // On Windows, if pathToCheck is already absolute with incorrect casing, fix it
+    if (os.platform() === 'win32' && path.isAbsolute(pathToCheck)) {
+      try {
+        resolvedInput = fs.realpathSync(pathToCheck);
+      } catch {
+        // Normalize the case by matching against targetDir
+        const normalizedPathToCheck = pathToCheck.toLowerCase();
+        const normalizedTargetDir = this.targetDir.toLowerCase();
+
+        if (normalizedPathToCheck.startsWith(normalizedTargetDir)) {
+          resolvedInput = this.targetDir + pathToCheck.substring(normalizedTargetDir.length);    
+        }
+      }
+    }
+
+    return fs.realpathSync(resolvedInput);
   } catch (e: unknown) {
     if (
       isNodeError(e) &&
       e.code === 'ENOENT' &&
       e.path &&
       !this.isFileSymlink(e.path)
     ) {
       return e.path;
     }
     throw e;
   }
 }

3. Fix isPathWithinRoot (line ~207)

 private isPathWithinRoot(
   pathToCheck: string,
   rootDirectory: string,
 ): boolean {
-  const relative = path.relative(rootDirectory, pathToCheck);
+  // Normalize both paths to lowercase on Windows for case-insensitive comparison
+  const normalizedRoot = os.platform() === 'win32'
+    ? rootDirectory.toLowerCase()
+    : rootDirectory;
+  const normalizedPath = os.platform() === 'win32'
+    ? pathToCheck.toLowerCase()
+    : pathToCheck;
+
+  const relative = path.relative(normalizedRoot, normalizedPath);
   return (
     !relative.startsWith(`..${path.sep}`) &&
     relative !== '..' &&
     !path.isAbsolute(relative)
   );
 }

Note: Add import * as os from 'node:os'; at the top of the file for both options.

What did you expect to happen?

  1. The CLI correctly validates WSL paths regardless of input casing
  2. File operations (read, write, list) work as expected

Client information

Client Information

Run gemini to enter the interactive CLI, then run the /about command.

> /about
╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│                                                                                                                                                  │
│ About Gemini CLI                                                                                                                                 │
│                                                                                                                                                  │
│ CLI Version                                       0.29.0-nightly.20260206.4ffc349c1                                                              │
│ Git Commit                                        4ffc349c1                                                                                      │
│ Model                                             auto-gemini-3                                                                                  │
│ Sandbox                                           no sandbox                                                                                     │
│ OS                                                win32                                                                                          │
│ Auth Method                                       gemini-api-key                                                                                 │
│                                                                                                                                                  │
╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯

Login information

No response

Anything else we need to know?

cc: @NTaylorMullen @joshualitt

Metadata

Metadata

Assignees

No one assigned

    Labels

    area/coreIssues related to User Interface, OS Support, Core Functionalitypriority/p1Important and should be addressed in the near term.windowsWindows OS related fix or improvement

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions