APM

>Agent Skill

@microsoft/cross-platform-paths

skillcode-quality

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.

pythontypescripttesting
apm::install
$apm install @microsoft/cross-platform-paths
apm::skill.md
---
name: cross-platform-paths
description: '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.'
argument-hint: Review path handling in [file or component]
user-invocable: false
---

# Cross-Platform Path Handling

**CRITICAL**: This extension runs on Windows, macOS, and Linux. Path bugs are the #1 source of issues.

## Core Rules

### Rule 1: Never Concatenate Paths with `/`

```typescript
// ❌ WRONG: POSIX-style path concatenation
const envPath = homeDir + '/.venv/bin/python';

// ✅ RIGHT: Use path.join()
const envPath = path.join(homeDir, '.venv', 'bin', 'python');
```

### Rule 2: Use path.resolve() for Comparisons, Not path.normalize()

```typescript
// ❌ 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;
```

### Rule 3: Use Uri.file().fsPath for VS Code Paths

```typescript
// ❌ 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) {
}
```

## Platform-Specific Gotchas

### Windows

| 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                     |

### macOS

| 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    |

### Linux

| 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           |

## Common Patterns

### Getting Platform-Specific Paths

```typescript
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
```

### Environment-Specific Executable Names

```typescript
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';
```

### Normalizing Paths for Comparison

```typescript
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
```

### Handling Uri | string Union Types

```typescript
// ❌ 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);
}
```

## File Existence Checks

```typescript
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
}
```

## Shell Path Escaping

```typescript
// ❌ 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;
```

## Testing Cross-Platform Code

When testing path-related code:

1. Test on Windows (cmd, PowerShell, Git Bash)
2. Test on macOS (zsh, bash)
3. Test on Linux (bash, fish)

Pay special attention to:

- Paths with spaces: `C:\Program Files\Python`
- Paths with Unicode: `~/проекты/`
- Very long paths (>260 chars on Windows)
- Paths with special characters: `$`, `&`, `(`, `)`