Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/main/utils/metadataExtraction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import * as readline from 'readline';
import { LocalFileSystemProvider } from '../services/infrastructure/LocalFileSystemProvider';
import { type ChatHistoryEntry, isTextContent, type UserEntry } from '../types';

import { translateWslMountPath } from './pathDecoder';

import type { FileSystemProvider } from '../services/infrastructure/FileSystemProvider';

const logger = createLogger('Util:metadataExtraction');
Expand Down Expand Up @@ -59,7 +61,7 @@ export async function extractCwd(
if ('cwd' in entry && entry.cwd) {
rl.close();
fileStream.destroy();
return normalizeDriveLetter(entry.cwd);
return normalizeDriveLetter(translateWslMountPath(entry.cwd));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Gate WSL path translation to local sessions

On Windows hosts this now rewrites every cwd read from a session file, including files coming from SshFileSystemProvider. ProjectPathResolver.resolveProjectPath() still calls extractCwd() for fsProvider.type === 'ssh', so a remote Linux project rooted at /mnt/d/repo will now be surfaced as D:/repo in ProjectScanner and session exports even though the session is remote. That loses the real remote path and can point path-based UI/actions at an unrelated local drive when someone SSHes into a WSL/Unix machine that uses /mnt/<drive> mounts.

Useful? React with 👍 / 👎.

}
}
} catch (error) {
Expand Down
22 changes: 21 additions & 1 deletion src/main/utils/pathDecoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,10 @@
}

// Ensure leading slash for POSIX-style absolute paths
return decodedPath.startsWith('/') ? decodedPath : `/${decodedPath}`;
const absolutePath = decodedPath.startsWith('/') ? decodedPath : `/${decodedPath}`;

Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change introduces Windows-only behavior (WSL mount translation) in decodePath()/extractCwd(), but there are no unit tests covering the translation cases (e.g., "/mnt/d/repo" -> "D:/repo", and "/mnt/d" -> "D:/"), nor a non-Windows no-op assertion. Since CI runs on both Ubuntu and Windows, adding conditional tests would help prevent regressions.

Suggested change
// On non-Windows platforms, return the POSIX-style absolute path unchanged
if (os.platform() !== 'win32') {
return absolutePath;
}

Copilot uses AI. Check for mistakes.
// Translate WSL mount paths to Windows drive-letter paths on Windows
return translateWslMountPath(absolutePath);
}

/**
Expand All @@ -91,6 +94,23 @@
return segments[segments.length - 1] || encodedName;
}

/**
* Translate WSL mount paths (/mnt/X/...) to Windows drive-letter paths (X:/...)
* when running on Windows. No-op on other platforms.
*/
export function translateWslMountPath(posixPath: string): string {
if (process.platform !== 'win32') {
return posixPath;
}
const match = /^\/mnt\/([a-zA-Z])(\/.*)?$/.exec(posixPath);

Check warning on line 105 in src/main/utils/pathDecoder.ts

View workflow job for this annotation

GitHub Actions / validate

Unsafe Regular Expression
if (match) {
const drive = match[1].toUpperCase();
const rest = match[2] ?? '';
Copy link

Copilot AI Mar 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

translateWslMountPath() returns "D:" for an input like "/mnt/d" (no trailing slash). On Windows, "D:" is not an absolute path (path.isAbsolute('D:') is false), which can break downstream path resolution and cause fallbacks back to POSIX paths. Consider defaulting the "rest" portion to "/" when the mount path has no trailing segment so the result is "D:/".

Suggested change
const rest = match[2] ?? '';
const rest = match[2] ?? '/';

Copilot uses AI. Check for mistakes.
return `${drive}:${rest}`;
Comment on lines +108 to +109

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

When translating a WSL path that points to the root of a drive (e.g., /mnt/c), match[2] is undefined, and rest becomes ''. This results in returning C:, which is not a valid absolute path and can cause issues with path joining later. The path should be C:/. You can handle this more concisely.

Suggested change
const rest = match[2] ?? '';
return `${drive}:${rest}`;
return `${drive}:${match[2] || '/'}`;

}
Comment on lines +101 to +110
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In Node.js on Windows, what does path.win32.isAbsolute('C:') return, and what is the correct absolute drive-root format?

💡 Result:

In Node.js on Windows, path.win32.isAbsolute('C:') returns false. The correct absolute drive-root format is 'C:\' or 'C:/' (drive letter followed by colon and path separator).

Citations:


🏁 Script executed:

# Find the pathDecoder.ts file and examine the function
find . -name "pathDecoder.ts" -type f | head -5

Repository: matt1398/claude-devtools

Length of output: 99


🏁 Script executed:

# Get the full context of the function and surrounding code
cat -n src/main/utils/pathDecoder.ts | sed -n '95,115p'

Repository: matt1398/claude-devtools

Length of output: 796


🏁 Script executed:

# Search for usage of translateWslMountPath in the codebase
rg "translateWslMountPath" -n -A 3 -B 1

Repository: matt1398/claude-devtools

Length of output: 1353


🏁 Script executed:

# Look for tests related to this function
find . -name "*.test.ts" -o -name "*.spec.ts" | xargs grep -l "translateWslMountPath" 2>/dev/null

Repository: matt1398/claude-devtools

Length of output: 52


🏁 Script executed:

# Check path.isAbsolute usage in the codebase to understand downstream expectations
rg "isAbsolute|path\..*Absolute" --type ts --type tsx -n | grep -E "(src/main|src/renderer)" | head -20

Repository: matt1398/claude-devtools

Length of output: 95


🏁 Script executed:

# Search for normalizeDriveLetter function definition
rg "normalizeDriveLetter" -n -B 2 -A 5 --type ts

Repository: matt1398/claude-devtools

Length of output: 1182


🏁 Script executed:

# Search for tests more carefully with proper syntax
fd "\.(test|spec)\.ts$" -x grep -l "translateWslMountPath\|pathDecoder"

Repository: matt1398/claude-devtools

Length of output: 146


🏁 Script executed:

# Look at the full context in metadataExtraction.ts to understand how the result is used
cat -n src/main/utils/metadataExtraction.ts | sed -n '60,70p'

Repository: matt1398/claude-devtools

Length of output: 483


🏁 Script executed:

# Check if pathDecoder has any imports or exports related to normalization
cat -n src/main/utils/pathDecoder.ts | head -50

Repository: matt1398/claude-devtools

Length of output: 2112


🏁 Script executed:

# Examine the pathDecoder test file to see existing test coverage
cat -n test/main/utils/pathDecoder.test.ts

Repository: matt1398/claude-devtools

Length of output: 9427


🏁 Script executed:

# Check if normalizeDriveLetter handles the slash issue
rg "normalizeDriveLetter" -A 10 -B 5 src/main/utils/metadataExtraction.ts | head -30

Repository: matt1398/claude-devtools

Length of output: 913


Normalize mount-root paths to X:/ format.

For input like /mnt/c, this currently returns C:, which is not an absolute path on Windows (path.win32.isAbsolute('C:') returns false). The function's contract explicitly documents the output format as X:/..., and downstream consumers assume absolute paths. Change line 108 from const rest = match[2] ?? ''; to const rest = match[2] ?? '/'; to produce C:/ instead.

Proposed fix
 export function translateWslMountPath(posixPath: string): string {
   if (process.platform !== 'win32') {
     return posixPath;
   }
   const match = /^\/mnt\/([a-zA-Z])(\/.*)?$/.exec(posixPath);
   if (match) {
     const drive = match[1].toUpperCase();
-    const rest = match[2] ?? '';
+    const rest = match[2] ?? '/';
     return `${drive}:${rest}`;
   }
   return posixPath;
 }

Also add test coverage for the /mnt/c case to prevent regression.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function translateWslMountPath(posixPath: string): string {
if (process.platform !== 'win32') {
return posixPath;
}
const match = /^\/mnt\/([a-zA-Z])(\/.*)?$/.exec(posixPath);
if (match) {
const drive = match[1].toUpperCase();
const rest = match[2] ?? '';
return `${drive}:${rest}`;
}
export function translateWslMountPath(posixPath: string): string {
if (process.platform !== 'win32') {
return posixPath;
}
const match = /^\/mnt\/([a-zA-Z])(\/.*)?$/.exec(posixPath);
if (match) {
const drive = match[1].toUpperCase();
const rest = match[2] ?? '/';
return `${drive}:${rest}`;
}
return posixPath;
}
🧰 Tools
🪛 GitHub Check: validate

[warning] 105-105:
Unsafe Regular Expression

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/utils/pathDecoder.ts` around lines 101 - 110, The
translateWslMountPath function returns "C:" for input "/mnt/c", which is not an
absolute Windows path; update the logic in translateWslMountPath to default the
captured tail to '/' instead of '' (change the rest assignment from match[2] ??
'' to match[2] ?? '/') so the function returns "C:/" for mount-root inputs, and
add a unit test for the "/mnt/c" case to assert the output is "C:/" (or
otherwise absolute per path.win32.isAbsolute) to prevent regression.

return posixPath;
}

// =============================================================================
// Validation
// =============================================================================
Expand Down
Loading