Skip to content

Paths

ActiveCli.Path.AbstractPath — a filesystem location, as some concrete flavour of path.

abstract class AbstractPath {
toString(): string;
abstract toWslPath(): string;
abstract isSpawnableAsCwd(): boolean;
}

Paths arrive at our boundaries as bare strings — from a caller’s options, from an IDE handing us a project root, from a CLI’s JSON output. A string cannot answer “are you a Windows drive path or a Linux one?”, so every caller that needs to know re-derives it, and the derivations drift apart. Here the answer is the class.

Member Description
toString() The path exactly as it was given, separators and all.
toWslPath() This location as the path a process running inside a WSL distro would see. Named for the destination rather than the source, because that is what the caller is choosing: “render yourself for a Linux child”. A path already native to that world answers with itself.
isSpawnableAsCwd() Whether spawning a child with this path as cwd is safe on the current host.

ActiveCli.Path.PathParser — turns a path string into the flavour it actually is. The boundary where a bare string stops being allowed to travel: call it the moment a path arrives, and pass the instance everywhere after.

class PathParser {
parse(raw: string): AbstractPath;
}
Input Result toWslPath()
//wsl.localhost/Ubuntu/home/user/proj WslUncPath /home/user/proj
/home/user/proj PosixPath /home/user/proj
C:\Users\foo WindowsPath /mnt/c/Users/foo
src\core (relative) PosixPath src/core

Anything not absolute in any flavour is treated as POSIX, which normalises separators on the way out and stays spawnable — the honest answer for a relative cwd.

PathParser is a separate class rather than a static on AbstractPath so the base does not have to import its own subclasses.

ActiveCli.Path.PosixPath — a Linux/macOS path (/home/user/proj), and the fallback for anything not absolute in another flavour.

class PosixPath extends AbstractPath {
constructor(raw: string);
toWslPath(): string; // separators normalised
isSpawnableAsCwd(): boolean; // always true
}

Already native to a POSIX child, so it is always safe as a spawn cwd. A relative path may still reach us with back-slashes (src\core), and separators are converted in toWslPath() rather than at construction — which keeps toString() honest about what we were given.

ActiveCli.Path.WindowsPath — a Windows drive path (C:\Users\foo, or the separator-less C:Users\foo).

class WindowsPath extends AbstractPath {
constructor(raw: string);
static matches(raw: string): boolean; // second character is ':'
toWslPath(): string;
isSpawnableAsCwd(): boolean; // always true
}

Inside a WSL distro the Windows drives are mounted under /mnt/<letter>, so that is what a Linux child must be handed.

parser.parse('D:\\Projects').toWslPath(); // '/mnt/d/Projects'
parser.parse('C:\\').toWslPath(); // '/mnt/c' — not '/mnt/c/'
parser.parse('C:Users\\foo').toWslPath(); // '/mnt/c/Users/foo'

The drive letter is lower-cased because the mount point is /mnt/c, not /mnt/C. A trailing slash is stripped because /mnt/c/ reads as a different location than the mount point itself.

ActiveCli.Path.WslUncPath — a WSL UNC path: \\wsl.localhost\Ubuntu\home\user\proj, or the legacy \\wsl$\NixOS\home\maicol07.

class WslUncPath extends AbstractPath {
static matches(raw: string): boolean;
static parse(raw: string): WslUncPath | null;
readonly distro: string;
readonly linuxPath: string;
toWslPath(): string; // the inner Linux path
isSpawnableAsCwd(): boolean; // always false
}

This is the path that breaks spawning, from both sides. isSpawnableAsCwd() is therefore always false: cmd.exe refuses a UNC cwd outright, and a process running inside the distro has no such location either.

WslUncPath.parse('\\\\wsl.localhost\\Ubuntu\\home\\user\\proj');
// distro 'Ubuntu', linuxPath '/home/user/proj'
WslUncPath.parse('\\\\wsl$\\NixOS\\home\\maicol07');
// distro 'NixOS', linuxPath '/home/maicol07'
WslUncPath.parse('\\\\wsl.localhost\\Ubuntu');
// distro 'Ubuntu', linuxPath '/'
WslUncPath.parse('\\\\server\\share\\file'); // null — a UNC path, but not a WSL one
WslUncPath.parse('/home/user'); // null

The distro id keeps its original casing, because wsl -d is case-sensitive about it. Only the \\wsl.localhost\ / \\wsl$\ host prefix is matched case-insensitively.

Forward-slashed input counts as a match: //wsl.localhost/Ubuntu/... is the exact form an IDE hands a backend, and it must not be mistaken for a POSIX path just because it starts with a slash.

SpawnOptions converts workingDirectory on the way in — the shortest place to turn a string into something that knows whether it can be a spawn cwd at all.

const options = new SpawnOptions({ workingDirectory: '//wsl.localhost/Ubuntu/home/me' });
options.workingDirectory?.isSpawnableAsCwd(); // false
options.workingDirectory?.toWslPath(); // '/home/me'

ClaudeAdapter omits an unspawnable cwd entirely rather than handing it over, because handing it over fails the spawn with ENOENT naming the directory rather than the binary — see Spawning failures.