-
Notifications
You must be signed in to change notification settings - Fork 11.7k
Description
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:
-
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 inconfig.ts:1857
- Added
-
6fb3b0900(2026-02-06) - "Shorten temp directory (Shorten temp directory #17901)"- Added
normalizePath()method inprojectRegistry.ts:70-76that normalizes to lowercase
- Added
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:
- Premature lowercase normalization: Paths are normalized to lowercase (e.g.,
\\wsl.localhost\ubuntu\...) before filesystem operations - Failed path resolution:
fs.realpathSync()fails because WSL paths are case-sensitive for UNC paths - the actual path is\\wsl.localhost\Ubuntu\...(capital U) - Path truncation: When
realpathSync()fails withENOENT, the error object'se.pathcontains a truncated path (e.g.,.../developmentinstead of.../development/github.com/google-gemini/gemini-cli) - 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?
- The CLI correctly validates WSL paths regardless of input casing
- 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