--- name: launch-wsl-claude-session description: >- Launch a detached, interactive Claude Code session inside WSL from a Windows Claude Code session — in a specific repo/folder, optionally remote-controllable and optionally seeded with an initial prompt. Use this WHENEVER the user wants to open / launch / spawn / start / fire off a separate (or background, detached, standalone, "and ignore it") Claude session in WSL, in a given directory, especially so it shows up in their remote Claude sessions list, even if they don't name every detail. It handles the Windows→WSL launch quirks that silently break naive attempts: ConPTY via Windows Terminal, PowerShell path passing, session-id vs initial-prompt openers, prompt quoting, and the workspace-trust gate. compatibility: >- Requires a Windows PC with WSL and Windows Terminal. Works whether Claude runs on the Windows host (via PowerShell) or inside the WSL distro (via bash + Windows interop). Not applicable on Claude.ai web, the mobile app, headless/remote sandboxes, macOS, or plain Linux without Windows underneath. --- # Launch a detached Claude session in WSL ## Environment requirement (read first) This skill only applies on a **Windows PC that has WSL** — and it works the same whether the Claude you're using right now is running **on the Windows host** or **inside that WSL distro**. Either way the result is identical: a new interactive Claude session in the chosen WSL directory. It does **not** apply, and you should not use it, when there's no local Windows+WSL to drive: Claude.ai web, the mobile app, a remote/headless sandbox, a Mac, or a plain Linux box without Windows underneath. In those environments, stop and tell the user the skill needs a Windows machine with WSL. **Pick the launcher for your host:** - **Claude running on Windows** (PowerShell available; platform is `win32`) → use `scripts\launch-wsl-claude.ps1`. - **Claude running inside WSL / Linux** (bash; `/proc/version` mentions `microsoft`) → use `scripts/launch-wsl-claude.sh`. Both scripts produce the same window via the same underlying command (`wt.exe wsl.exe --cd -- ...`); they differ only in how the host shell spawns it. ## What this does Opens a new terminal window running an **interactive** `claude` session inside WSL, rooted at a directory you choose, and leaves it running for the user to drive (or to control remotely from the Claude mobile/web app). The session is independent — it shares no context with the current one. Two ways to open the session: - **Default — `--session-id `.** Opens a brand-new session directly. This bypasses the "agents view" landing screen (which otherwise does *not* start a chat session) and gives the user an empty session to type into. - **Initial-prompt mode — a positional prompt.** If the user supplies an initial prompt (e.g. "stand by for instructions"), seed the session with it instead. Do **not** pass `-p`/`--print` — that makes Claude run the prompt once and exit. Without it, the prompt becomes the first message and the session stays interactive. ## The fastest path: use the bundled script **On a Windows host** (PowerShell): ```powershell # Default (session-id opener): & "\scripts\launch-wsl-claude.ps1" -Dir /home/passp/repos/GHA-bench # With an initial prompt (stays interactive): & "\scripts\launch-wsl-claude.ps1" -Dir /home/passp/repos/GHA-bench -Prompt "Stand by for instructions." ``` Parameters: `-Dir` (required, the **WSL** path), `-Prompt` (optional initial prompt), `-Distro` (default `Ubuntu`), `-RemoteControlName` (optional, adds `--remote-control `), `-NoWindowsTerminal` (switch; avoid — see the ConPTY gotcha). **Inside WSL / Linux** (bash) — same behavior, flag-style args: ```bash # Default (session-id opener): bash "/scripts/launch-wsl-claude.sh" --dir /home/passp/repos/GHA-bench # With an initial prompt (stays interactive): bash "/scripts/launch-wsl-claude.sh" --dir /home/passp/repos/GHA-bench --prompt "Stand by for instructions." ``` Args: `--dir` (required), `--prompt` (optional), `--distro` (default `Ubuntu`), `--remote-control-name` (optional). Both scripts resolve the `claude` binary path, generate the session UUID, pass an initial prompt as a single argument (so the quoting is always correct), and launch a Windows Terminal window — so you don't have to reconstruct any of it by hand. ## Prerequisites (check these — they cause silent failures) 1. **The target directory must be trusted.** If `hasTrustDialogAccepted` is `false` for that path in WSL `~/.claude.json`, the session opens but immediately **blocks on the workspace-trust dialog** waiting for input. A "launch and ignore" session then sits there invisibly and never registers for remote control. Either open it once interactively and click **Trust**, or (only with the user's explicit OK) set `hasTrustDialogAccepted: true` for that exact path in `~/.claude.json` first. Do not silently flip this — it's a security gate and the user's decision. 2. **Claude Code must be installed in the WSL distro** (the script checks via `command -v claude` and errors out if missing). ## Remote control The whole point is usually that the session shows up in the user's **remote Claude sessions list**. Remote control engages automatically at startup when `remoteControlAtStartup: true` is set in the WSL `~/.claude/settings.json` (or `~/.claude.json`). If it is, you don't need any flag — just launch. If it isn't, pass `-RemoteControlName ` so the script adds `--remote-control `. Note the session only registers once it actually *reaches a live session* — i.e. past the agents-view landing (handled by the openers above) and past the trust gate (prerequisite #1). If "nothing shows up," it's almost always one of those two gates. ## Why the launch is done this way (gotchas, learned the hard way) These are non-obvious and each one silently breaks the launch if ignored — that's why the script encodes them: - **On the Windows host, launch from PowerShell, never the Bash/Git-Bash tool.** Git Bash rewrites POSIX-looking arguments: `/home/passp/.local/bin/claude` becomes `C:/Program Files/Git/home/passp/.local/bin/claude`. The session then starts in the wrong place (or the binary isn't found). PowerShell `Start-Process` passes the paths through untouched. (This mangling is a Git-Bash/MSYS quirk — **real WSL bash does not do it**, which is why the WSL-side `.sh` launcher calls `wt.exe` from bash directly.) - **Use Windows Terminal (`wt.exe`), not bare `wsl.exe`.** `wt` gives the session a proper ConPTY. A bare `wsl.exe` spawn gets a malformed TTY (`your 131072x1 screen size is bogus`), and in initial-prompt mode Claude treats that as non-interactive and **exits immediately**. With `wt`, the prompt session stays open. - **Quote the entire initial prompt as ONE argument.** If the prompt is split across multiple shell arguments, Claude receives only the first word. The script takes `-Prompt` as a single string and passes it as a single element, so this is handled — but if you ever launch by hand, wrap the whole prompt in quotes: `... -- "Stand by for instructions."` not `... Stand by for instructions.` - **Use the full path to the `claude` binary.** A non-login WSL shell may not have `~/.local/bin` on `PATH`. The script resolves the absolute path first. - **Inject the full login PATH via `env` — never via an interactive shell wrapper.** Resolving the binary isn't enough: `wsl.exe -- ` runs claude under WSL's reduced default PATH, so the *running* agent's own subprocesses can't find tools that only live on the login PATH — `pwsh` (`/snap/bin`), `bun` (`~/.bun/bin`), `dotnet`, `~/.npm-global/bin`. A long agent job (e.g. a benchmark) then silently breaks with `pwsh: command not found`. The scripts capture the login PATH from an **interactive** login shell (`bash -lic` — `bun`/`~/.npm-global/bin` are added in `~/.bashrc`, which a plain `-lc` skips) and inject it with `... -- env "PATH=" `. Do **not** instead wrap claude in `bash -lic 'exec "$@"'`: an interactive bash grabs the ConPTY's process group and the claude **TUI exits immediately**. `env` is a transparent exec, so claude stays a direct child holding the ConPTY (like the working bare launch), just with the right PATH. - **`--cd ` sets the working directory** for the session; pass a WSL path (`/home/...`), not a Windows path. ## Manual one-liners (fallback if the script isn't available) ```powershell # Default — new session by id: $sid = [guid]::NewGuid().ToString() $claude = (wsl.exe -d Ubuntu -- bash -lc 'command -v claude').Trim() Start-Process wt.exe -ArgumentList @('wsl.exe','-d','Ubuntu','--cd','/home/passp/repos/GHA-bench','--',$claude,'--session-id',$sid) # Initial-prompt — note the prompt is a single quoted argument: $claude = (wsl.exe -d Ubuntu -- bash -lc 'command -v claude').Trim() Start-Process wt.exe -ArgumentList @('wsl.exe','-d','Ubuntu','--cd','/home/passp/repos/GHA-bench','--',$claude,'Stand by for instructions.') ``` Inside WSL / Linux (bash, via Windows interop): ```bash # Default — new session by id: wt.exe wsl.exe -d Ubuntu --cd /home/passp/repos/GHA-bench -- "$(command -v claude)" \ --session-id "$(cat /proc/sys/kernel/random/uuid)" & # Initial-prompt — the whole prompt is a single quoted argument: wt.exe wsl.exe -d Ubuntu --cd /home/passp/repos/GHA-bench -- "$(command -v claude)" \ "Stand by for instructions." & ``` After launching, the session is the user's to drive — don't try to interact with it from here. If they asked you to "launch and ignore," confirm it's up and stop.