Fix: plugin marketplace installLocation should use portable paths (cross-platform path breakage)

Resolved 💬 3 comments Opened Mar 20, 2026 by matteo-michele-bianchini Closed May 24, 2026

Bug: Cross-platform installLocation in known_marketplaces.json creates mangled directories

Problem

When Claude Code runs inside a Linux container (Docker, WSL2, devcontainer) but ~/.claude is mounted from a Windows host, the plugin marketplace system creates a mangled directory in the project's working directory.

Root cause: known_marketplaces.json is written by the VS Code extension on the Windows host with an absolute Windows path:

{
  "claude-plugins-official": {
    "installLocation": "C:\\Users\\matte\\.claude\\plugins\\marketplaces\\claude-plugins-official",
    ...
  }
}

When the CLI reads this on Linux:

  1. path.isAbsolute("C:\\Users\\matte\\...") returns false (no / prefix)
  2. The path is treated as relative to CWD
  3. git clone creates a literal directory named C:\Users\matte\.claude\plugins\marketplaces\claude-plugins-official inside the project

Proof — my workspace right now:

$ ls -d /workspace/C*
/workspace/C:\Users\matte\.claude\plugins\marketplaces\claude-plugins-official

Environment

  • Windows 11 host with VS Code + Claude Code extension
  • Docker container (node:20-slim, Linux) with ~/.claude bind-mounted from host
  • Claude Code CLI v2.1.79
  • Also reproducible on WSL2 (see #26517) and macOS↔devcontainer (#15717)

Related issues (all closed without fix)

  • #30114 — my original report, closed as duplicate of ↓
  • #26517 — WSL2 variant, closed as duplicate of ↓
  • #24503 — Docker variant, closed as duplicate of ↓
  • #15717 — original issue (macOS/devcontainer), closed as stale

Suggested fix

The installLocation should be portable across platforms. Two approaches (not mutually exclusive):

Option A: Store relative paths (recommended)

Store installLocation relative to ~/.claude/ instead of as an absolute path:

- "installLocation": "C:\\Users\\matte\\.claude\\plugins\\marketplaces\\claude-plugins-official"
+ "installLocation": "plugins/marketplaces/claude-plugins-official"

At read time, resolve relative to the current ~/.claude/ (or CLAUDE_CONFIG_DIR):

function resolveInstallLocation(loc: string): string {
  if (path.isAbsolute(loc)) return loc; // backward compat
  return path.join(getConfigDir(), loc);
}

This makes the config inherently portable across Windows/Linux/macOS and across users.

Option B: Detect and sanitize cross-platform paths at read time

When reading installLocation, detect Windows-style paths on Linux (and vice versa) and re-resolve:

function normalizeInstallLocation(loc: string, marketplaceName: string): string {
  const isWindows = process.platform === 'win32';
  const looksLikeWindowsPath = /^[A-Za-z]:[\\\/]/.test(loc);
  
  if (!isWindows && looksLikeWindowsPath) {
    // Windows path on Linux — re-derive from marketplace name
    return path.join(getMarketplacesDir(), marketplaceName);
  }
  
  const looksLikePosixAbsolute = loc.startsWith('/');
  if (isWindows && looksLikePosixAbsolute && !fs.existsSync(loc)) {
    return path.join(getMarketplacesDir(), marketplaceName);
  }
  
  return loc;
}
Additional guard: validate before git clone

The existing corruption check in s86 (marketplace refresh) should also run before cloning:

const resolved = path.resolve(installLocation);
const marketplacesDir = path.resolve(getMarketplacesDir());

if (!resolved.startsWith(marketplacesDir)) {
  // Path would escape the marketplaces directory — re-derive it
  installLocation = path.join(marketplacesDir, marketplaceName);
}

This prevents git clone from ever writing outside ~/.claude/plugins/marketplaces/.

Impact

This affects every user running Claude Code CLI in a Linux environment with ~/.claude shared from a Windows host — a very common Docker/devcontainer setup. The bug:

  • Pollutes the project directory with a junk folder
  • May cause git noise (unless .gitignored)
  • Breaks plugin discovery (plugins show as "not found")
  • Has been reported 4 times over 3+ months with no resolution

View original on GitHub ↗

This issue has 3 comments on GitHub. Read the full discussion on GitHub ↗