.github/skills/cross-platform-paths/SKILL.md
Critical patterns for cross-platform path handling in this VS Code extension. Windows vs POSIX path bugs are the #1 source of issues. Use this skill when reviewing or writing path-related code.
npx skillsauth add microsoft/vscode-python-environments cross-platform-pathsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
CRITICAL: This extension runs on Windows, macOS, and Linux. Path bugs are the #1 source of issues.
/// ❌ WRONG: POSIX-style path concatenation
const envPath = homeDir + '/.venv/bin/python';
// ✅ RIGHT: Use path.join()
const envPath = path.join(homeDir, '.venv', 'bin', 'python');
// ❌ WRONG: path.normalize keeps relative paths relative on Windows
const normalized = path.normalize(fsPath);
// path.normalize('\test') → '\test' (still relative!)
// ✅ RIGHT: path.resolve adds drive letter on Windows
const normalized = path.resolve(fsPath);
// path.resolve('\test') → 'C:\test' (absolute!)
// When comparing paths, use resolve() on BOTH sides:
const pathA = path.resolve(fsPath);
const pathB = path.resolve(e.environmentPath.fsPath);
return pathA === pathB;
// ❌ WRONG: Raw string comparison
if (filePath === otherPath) {
}
// ✅ RIGHT: Compare fsPath to fsPath
import { Uri } from 'vscode';
const fsPathA = Uri.file(pathA).fsPath;
const fsPathB = Uri.file(pathB).fsPath;
if (fsPathA === fsPathB) {
}
| Issue | Details |
| ------------------ | -------------------------------------------- |
| Drive letters | Paths start with C:\, D:\, etc. |
| Backslashes | Separator is \, not / |
| Case insensitivity | C:\Test equals c:\test |
| Long paths | Paths >260 chars may fail |
| Mapped drives | Z:\ may not be accessible |
| pyenv-win | Uses pyenv.bat, not pyenv or pyenv.exe |
| Poetry cache | %LOCALAPPDATA%\pypoetry\Cache\virtualenvs |
| UNC paths | \\server\share\ format |
| Issue | Details |
| ----------------- | ------------------------------------------- |
| Case sensitivity | Depends on filesystem (usually insensitive) |
| Homebrew symlinks | Complex symlink chains in /opt/homebrew/ |
| Poetry cache | ~/Library/Caches/pypoetry/virtualenvs |
| XCode Python | Different from Command Line Tools Python |
| Issue | Details |
| ---------------- | --------------------------------------- |
| Case sensitivity | Paths ARE case-sensitive |
| /bin symlinks | /bin may be symlink to /usr/bin |
| XDG directories | ~/.local/share/virtualenvs for pipenv |
| Poetry cache | ~/.cache/pypoetry/virtualenvs |
| Hidden files | Dot-prefixed files are hidden |
import * as os from 'os';
import * as path from 'path';
// Home directory
const home = os.homedir(); // Works cross-platform
// Construct paths correctly
const venvPath = path.join(home, '.venv', 'bin', 'python');
// Windows: C:\Users\name\.venv\bin\python
// macOS: /Users/name/.venv/bin/python
// Linux: /home/name/.venv/bin/python
const isWindows = process.platform === 'win32';
// Python executable
const pythonExe = isWindows ? 'python.exe' : 'python';
// Activate script
const activateScript = isWindows
? path.join(venvPath, 'Scripts', 'activate.bat')
: path.join(venvPath, 'bin', 'activate');
// pyenv command
const pyenvCmd = isWindows ? 'pyenv.bat' : 'pyenv';
import { normalizePath } from './common/utils/pathUtils';
// Use normalizePath() for map keys and comparisons
const key = normalizePath(filePath);
cache.set(key, value);
// But preserve original for user display
traceLog(`Discovered: ${filePath}`); // Keep original
// ❌ WRONG: Assuming Uri
function process(locator: Uri | string) {
const fsPath = locator.fsPath; // Crashes if string!
}
// ✅ RIGHT: Handle both types
function process(locator: Uri | string) {
const fsPath = locator instanceof Uri ? locator.fsPath : locator;
// Now normalize for comparisons
const normalized = path.resolve(fsPath);
}
import * as fs from 'fs';
import * as path from 'path';
// Check file exists (cross-platform)
const configPath = path.join(projectRoot, 'pyproject.toml');
if (fs.existsSync(configPath)) {
// File exists
}
// Use async version when possible
import { promises as fsPromises } from 'fs';
try {
await fsPromises.access(configPath);
// File exists
} catch {
// File does not exist
}
// ❌ WRONG: Unescaped paths in shell commands
terminal.sendText(`python ${filePath}`);
// D:\path\file.py becomes "D:pathfile.py" in some shells!
// ✅ RIGHT: Quote paths
terminal.sendText(`python "${filePath}"`);
// For Git Bash on Windows, escape backslashes
const shellPath = isGitBash ? filePath.replace(/\\/g, '/') : filePath;
When testing path-related code:
Pay special attention to:
C:\Program Files\Python~/проекты/$, &, (, )development
VS Code settings precedence rules and common pitfalls. Essential for any code that reads or writes settings. Covers getConfiguration scope, inspect() vs get(), and multi-workspace handling.
tools
Run smoke tests to verify extension functionality in a real VS Code environment. Use this when checking if basic features work after changes.
development
Run the mandatory pre-commit checks before committing code. Includes lint, type checking, and unit tests. MUST be run before every commit.
tools
Run integration tests to verify that extension components work together correctly. Use this after modifying component interactions or event handling.