はじめに
Windows + WSL2 で開発している方には、こんな悩みがありませんか?
- 開発環境(Node.js, Python, Go など)はすべて WSL 側に揃えてある
- でも Claude Code Desktop の Local 環境を選ぶと、Windows ネイティブの
claude.exeが起動してしまい、WSL 側の dev stack にアクセスできない - かといって CLI の
claudeを WSL ターミナルで使い続けると、結局 VSCode で差分を見て、別ターミナルでログを追って、と4窓運用になりがち
この記事では、Claude Code Desktop の SSH 環境機能 を使って、WSL 内の Claude Code を Desktop GUI から呼び出す方法を解説します。ローカル完結(外部公開なし)の安全な構成で、mirrored networking 前提です。
なぜ Desktop 版を WSL で使いたいのか
「CLI で足りてる」と思った方、ちょっと立ち止まってください。今、VSCode 開いてませんか? 差分はそっちで見てませんか?別ターミナルで tail -f していませんか?
「CLI で完結している」ように見えても、実態は次のような構成だったりします。
- Claude Code CLI(プロンプト・実行)
- VSCode(差分確認、ファイルツリー、エディタ)
- 別ターミナル(git status, ログ tail, dev server)
- ブラウザ(preview, GitHub PR)
これを Alt+Tab で切り替えながら回している、という方が多いはずです。視線と認知が4窓に分散している状態です。
Claude Code Desktop はこれらを1ウィンドウに集約します。
- diff view → VSCode の代わり
- 統合ターミナル → 別ターミナルの代わり
- live preview → ブラウザの代わり
- PR モニタリング → GitHub タブの代わり
- Code / Cowork / Chat タブの三層切替 → 別タスク並走
「Claude Code が GUI を持った」のではなく、「普段 CLI 使いが暗黙に開いている補助 GUI を全部内包した」というのが正確な理解だと感じます。
CLI と Desktop の使い分け
ただし、CLI を全廃するという話ではありません。両者には明確な得意分野があります。
CLI が勝つシーン
- 最新機能が先に来る:新フラグ・新モデル対応・実験機能は CLI が先行することが多い
- SSH 越しに動く:tmux/byobu/screen に入れて detach できる。SSH が切れても継続できる
- ほったらかしが得意:長尺タスクを夜投げて朝確認する運用が自然
- 常駐コストが低い:Electron アプリの起動分の電力・メモリが節約できる
Desktop が勝つシーン
- diff レビューの認知コスト:ターミナルの色付き diff より GUI が圧倒的に楽
- 並列セッション管理:Git worktree ごとのセッションをサイドバーで一覧できる
- 三層タブ切替:Code(本作業)/ Cowork(別タスクのバックグラウンド実行)/ Chat(壁打ち)を1ウィンドウで切り替え
- PR モニタリング:CI 状態を見ながら自動修正・自動マージまで GUI で完結
シーンによって使い分けるのが現実解で、本記事は「Desktop を使いたいシーン」を WSL 環境で実現するための手順です。
Codex アプリとの比較(参考)
筆者の使用感ベースの話になりますが、OpenAI の Codex アプリは WSL ネイティブ対応している一方で、ネイティブと WSL の切替に再起動が必要で、SSH 接続機能も今のところ見当たりません。
Claude Code Desktop は WSL ネイティブ対応こそ未実装ですが、SSH 環境機能を持っているため、本記事の方法で実質的に WSL 環境を扱えます。さらに次のような利点があります。
- Windows ネイティブのプロジェクトと WSL のプロジェクトを並列セッションで同時に開ける
- 切替に再起動が不要
- 将来的にリモート Linux サーバーを使うときも同じ仕組みで対応可能
「ネイティブ対応の有無」だけ見ると Codex の方が手厚く見えますが、実用上の柔軟性では SSH 経路を持つ Claude Code Desktop が勝る場面も多そうです。
環境・前提条件
- OS: Windows 11
- WSL2: Ubuntu(systemd 有効化済み)
- networkingMode: mirrored(
%USERPROFILE%\.wslconfigで設定) - Claude Code Desktop: 最新版(SSH 環境対応)
- Claude Code: WSL 内に native install 済み(
~/.local/bin/claude) - SSH 鍵: Windows 側に ed25519 鍵を作成済み
構築手順
1. WSL に openssh-server をインストール
sudo apt update && sudo apt install -y openssh-server
sudo ssh-keygen -A # ホスト鍵生成(既にあればスキップされる)
2. systemd の有効化を確認
/etc/wsl.conf に次の設定があることを確認します。
[boot]
systemd=true
設定後、PowerShell から WSL を再起動します。
wsl --shutdown
3. sshd_config を編集
/etc/ssh/sshd_config を次のように編集します。
Port 2222
ListenAddress 127.0.0.1
PasswordAuthentication no
PubkeyAuthentication yes
PermitRootLogin no
ポイントは ListenAddress 127.0.0.1 です。mirrored networking でも 127.0.0.1 だけにバインドしておけば、誤って外部からアクセスされるリスクが排除できます。「設定で守る」より「そもそも待ち受けない」が一段強い設計です。
ポートを 2222 にしているのは、Windows 側に OpenSSH Server が動いている場合の競合回避と、デフォルトポートを狙ったスキャンの回避が理由です。
構文チェックを忘れずに行います。
sudo sshd -t # 無音なら OK
4. 鍵を設置(WSL 側から /mnt/c 経由)
ここでひとつ知見ポイントです。Windows 側から PowerShell の Get-Content や cmd の type でパイプして鍵を渡す方法もあるのですが、PowerShell のバージョンによってパイプのエンコーディングが異なるため、BOM が付与されたり改行コードが変換されたりする事故が起こりえます。
WSL 側から /mnt/c 経由で読むと、9P プロトコル越しにバイト列そのまま読めるため変換層が一切挟まりません。これが一番事故が少ない方法です。
mkdir -p ~/.ssh && chmod 700 ~/.ssh
cat /mnt/c/Users/{Windowsユーザー名}/.ssh/id_ed25519.pub >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
# 確認
cat ~/.ssh/authorized_keys
wc -l ~/.ssh/authorized_keys # 1 と出ればOK
5. sshd を起動
sudo systemctl enable --now ssh
sudo systemctl status ssh # active (running) を確認
ss -tlnp | grep 2222 # 127.0.0.1:2222 で LISTEN を確認
6. Windows 側から疎通確認
PowerShell から接続テストします。
ssh -p 2222 yourname@localhost
mirrored networking なので localhost で WSL に届きます(WSL の IP を調べる必要はありません)。鍵のパスフレーズだけ聞かれて入れれば成功です。
7. Claude Code Desktop の SSH 設定
Desktop の Code タブから SSH 接続を追加します。
| 項目 | 入力値 |
|---|---|
| 名前 | WSL(任意の識別名) |
| SSH ホスト | yourname@localhost |
| SSH ポート | 2222 |
| ID ファイル | C:\Users\{Windowsユーザー名}\.ssh\id_ed25519 |
注意点として、ID ファイルは絶対パスで指定します。%USERPROFILE%\.ssh\id_ed25519 のような環境変数記法は展開されません。
8. 自動起動の設定(重要)
ここが地味に効きます。WSL2 は誰も WSL を開いていないと VM が一定時間で落ちる仕様で、当然 sshd も一緒に止まります。Claude Code Desktop から繋ごうとして「あれ、繋がらない」となるパターンです。
対策は2段構えで、両方やっておくと無人で安定します。
(a) WSL のアイドルタイムアウトを無効化
%USERPROFILE%\.wslconfig に追記します。
[wsl2]
vmIdleTimeout=-1
-1 で「タイムアウトしない」設定になります。これで WSL VM がアイドルで落ちることがなくなり、sshd も常時生きたままになります。
設定を反映するには PowerShell から wsl --shutdown を一度実行します。
(b) Windows ログオン時に WSL を自動起動
OS 再起動後に手動で WSL を立ち上げないと sshd が動かない、という状況を避けるため、タスクスケジューラで自動起動させます。
タスクスケジューラを開いて新規タスクを作成し、次のように設定します。
- 全般:「最上位の特権で実行する」にチェック、「非表示」にチェック
- トリガー:「ログオン時」
- 操作:プログラムに
wsl.exe、引数に-d Ubuntu --exec sleep infinity
引数の --exec sleep infinity で WSL を「起こしっぱなし」にします。sshd を直接起動するのではなく、systemd 配下の ssh.service が自動起動するので WSL VM さえ立ち上げれば十分です。
この (a) + (b) で「Windows 起動 → WSL VM 自動起動 → systemd が sshd を起動 → 127.0.0.1:2222 で LISTEN」が完全無人で成立します。Claude Code Desktop からいつでも繋がる状態になります。
ハマったポイント
zsh の .zshenv と .zprofile の挙動差
これが一番ハマりました。
WSL 側で ~/.zprofile に PATH を書いていたのに、SSH 越しの which claude が claude not found を返す。手元の対話シェルでは普通に通る、という症状です。
原因は、SSH のコマンド実行で起動される zsh が non-login かつ non-interactive モードだったことでした。次のコマンドで確認できます。
ssh -p 2222 yourname@localhost 'echo "login=$([[ -o login ]] && echo yes || echo no)"; echo "interactive=$([[ -o interactive ]] && echo yes || echo no)"'
筆者の環境では login=no interactive=no が返ってきました。
zsh の設定ファイルの読み込み順は意外と複雑です。
| ファイル | 読まれる条件 |
|---|---|
~/.zshenv | 常に(login/non-login, interactive/non-interactive 問わず) |
~/.zprofile | login shell のみ |
~/.zshrc | interactive shell のみ |
~/.zlogin | login shell のみ(zshrc の後) |
つまり、SSH のコマンド実行時に確実に読まれるのは ~/.zshenv だけ です。
bash の ~/.profile と同じ感覚で ~/.zprofile に書いていたのが落とし穴でした。~/.zshenv に移すと一発で解決します。
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshenv
SSH ホスト欄の書式
Claude Code Desktop の SSH 接続追加画面で「SSH ホスト」欄に localhost だけ書くと、ユーザー名が解決できずに失敗します。プレースホルダ通り yourname@localhost という user@hostname 形式で書く必要があります。
%USERPROFILE% は展開されない
ID ファイルのパスに %USERPROFILE%\.ssh\id_ed25519 と書いても展開されません。設定ファイル AppData\Roaming\Claude\ssh_configs.json を直接見ると、入力した文字列がそのまま JSON 値として保存されているのが分かります。絶対パス C:\Users\{ユーザー名}\.ssh\id_ed25519 で指定しましょう。
動作確認
Claude Code Desktop の SSH 接続が成功すると、Code タブから WSL 内のフォルダ(例:/home/yourname/projects/...)が選択でき、その中で claude セッションが立ち上がります。
簡単に「接続できてますか」と聞くだけで、現在のプロジェクト名や worktree 名を返してくれることで動作確認できます。
セキュリティの観点
今回の構成では sshd を 127.0.0.1:2222 でしか LISTEN していないため、mirrored networking でも LAN や外部からアクセスされることはありません。
- Windows Firewall に WSL 用の inbound 許可ルールを作らない
- sshd 側で
ListenAddress 127.0.0.1を明示 - パスワード認証無効、公開鍵認証のみ
- root ログイン無効
外部から触りたくなったとき(出先の Mac や Android から繋ぎたい等)は、Tailscale を WSL に入れて tailnet ノード化するルートが安全で柔軟です。今回はローカル完結で十分なので、こうした拡張は将来の選択肢として残しておきます。
まとめ
今回のポイントを整理します。
- Claude Code Desktop の SSH 環境機能を使えば、WSL ネイティブ未対応でも WSL の dev stack を活用できる
- mirrored networking +
ListenAddress 127.0.0.1でローカル完結の安全構成が組める - 鍵の設置は WSL から
/mnt/c経由が一番事故が少ない - zsh の SSH コマンド実行は non-login non-interactive で起動するため、PATH は
~/.zshenvに書く必要がある - CLI と Desktop は競合ではなく補完。シーンで使い分けるのが現実解
CLAUDE_CODE_USE_POWERSHELL_TOOL=1 のような Windows ネイティブ対応を示唆する環境変数も Claude Code に登場し始めているので、いずれ WSL のネイティブ対応が入るかもしれません。それまでの繋ぎとして、SSH 経路は十分実用的だと感じました。
同じように WSL に dev 環境を寄せている方の参考になれば幸いです。

コメント