外から手元のマシンで作業を続けたい、という話をするとき、たいてい「どうやって起動するか」に注目が集まります。起動さえできればあとは動かせるでしょ、と思いますよね。でも実際に外へ持ち出してみると、先に壁になるのはもっと手前——「そもそも、外から手元のマシンにちゃんと届いているのか」という、回線そのものの話でした。
別の記事で、iPhoneから手元のセッションを1コマンドで立ち上げる「起動」の側を扱いました(iPhoneから手元のClaude Codeを1タップで起動する)。あれはあくまで入口を開けるところまで。この記事はその一段下、開けた入口まで安定して信号が通る「回線」をどう組むかの側です。起動の話はそちらに預けて、ここでは繰り返しません。
前提はシンプルです。開発マシンは家に置きっぱなしで、そこにAIエージェント(Claude Code など。Pro $20/月、Max $100〜200/月)や普段の作業環境が常駐している。自分は電車のなかだったり、カフェだったり、とにかく別の場所にいる。この「数キロの距離」を、どのレイヤーで、どう埋めるか。
この記事で扱うこと
- 電車のトンネルで回線が切れても、再接続の操作なしで復帰する接続の作り方
- グローバルIPやポート開放に頼らず、CLIだけでインターネット越しのSSHを通す方法
- リモートでコピーしたはずのテキストが手元に届かない、クリップボードの落とし穴とその対処
順番に、下のレイヤーから積んでいきます。
レイヤー1:切れる前提で組む接続(mosh)
まず素のSSHで外から入ろうとすると、モバイル回線ではすぐに現実にぶつかります。電車がトンネルに入る、Wi-Fiとモバイルを切り替える、しばらく画面を放置する。そのたびにセッションが固まり、Ctrl-C も効かず、結局ターミナルを閉じてつなぎ直す。数十秒の作業のために、その前後で毎回この儀式が要る。
ここで効くのが mosh です。パッケージ定義には、採用理由をそのままコメントで残してあります。
commonPackages = with pkgs; [
coreutils
curl
git
mosh # 断続的接続・ローミングに強い SSH 代替。電車のトンネル等で回線が切れても再接続不要で復帰する
];
moshは接続をIPアドレスではなくセッションに紐づけるので、回線が切れてIPが変わっても同じセッションに戻れます。トンネルを抜けた瞬間、何も操作しないうちに画面が生き返る。「つなぎ直す」という動作そのものが消えるのが、モバイルからだと想像以上に効きます(要は座席の上で親指がフリーズしなくて済むというだけの話なんですけど、これが地味に大きい)。
ひとつ、配布まわりで踏んだ穴も書いておきます。moshで入ってきた先のコマンドは、対話シェルではなく非対話のシェルとして起動するため、普段のログイン設定が読まれずPATHが痩せます。そのままだと、手元では動くツールがmosh越しだと「見つからない」。なので環境変数側で明示的にPATHを通しておく必要がありました。
# SSH remote commands such as mosh-server run under non-interactive zsh.
export PATH="$HOME/.nix-profile/bin:/etc/profiles/per-user/$USER/bin:/run/current-system/sw/bin:/nix/var/nix/profiles/default/bin:$PATH"
「手元では動くのに、外から入ると動かない」の多くは、この非対話シェル問題です。回線が通ったあとの、二つ目の落とし穴として覚えておくと早く抜けられます。
レイヤー2:どこからでも届くVPN(Tailscale)
moshで「切れても戻れる」は解決しましたが、その手前に「そもそも外から自宅マシンに到達できるのか」という問題があります。家のグローバルIPは変わるし、ルーターのポートを開けてSSHを世界に晒すのは、守りの観点でやりたくない。
ここは Tailscale に任せています。VPNというと重たい常駐クライアントを思い浮かべますが、実際どうなの?——設定はこれだけです。
# Tailscale VPN(CLIのみ、インターネット越しSSH用)
services.tailscale.enable = true;
ポイントは、コメントに書いた通りCLIだけで完結するところ。GUIのVPNクライアントを常駐させるのではなく、宣言的な設定に1行足すだけで、自分のデバイス同士がプライベートなネットワークで直接つながります。外にいるスマホやノートからも、家の開発マシンが「同じLANの隣の席にいる」ように見える。グローバルIPの追跡もポート開放も要らず、mosh/SSHはこのVPNの内側を通します。
構成を絵にすると、こういう積み方です。
到達性は Tailscale、接続の粘り強さは mosh。役割を分けておくと、片方が不調のときにどちらを疑うかがはっきりします。
レイヤー3:クリップボードの落とし穴
回線が通り、セッションも粘る。ここまで来て最後にハマったのが、地味だけれど毎回効いてくるクリップボードでした(回線でもVPNでもなく、正直ここが一番しょうもない)。
症状はこうです。リモート先のエディタ(neovim/LazyVim)でテキストをコピーする。手元に貼ろうとすると、何も入っていない。コピーは成功しているように見えて、実際は手元の端末に届いていない。
原因は接続の種類の見分け方にありました。neovim/LazyVimは環境変数$SSH_TTYを見て「これはリモート接続だ」と判定し、その場合はOSC52というターミナル経由でクリップボードを手元へ転送します。ところが——moshは$SSH_TTYをセットしないのです。結果、エディタは「ローカルだ」と誤認してpbcopy(=リモートホスト側のクリップボード)に書き込み、手元には一切届かない。
対処は「接続種別を当てにせず、常にOSC52を使う」ことでした。設定にも、なぜそうしているかをコメントで残してあります。
-- クリップボード: mosh 越しでも手元の端末へ OSC52 で転送する。
-- neovim/LazyVim は $SSH_TTY でリモート判定して OSC52 を有効化するが、mosh は
-- SSH_TTY をセットしないため pbcopy(=リモートホストのクリップボード)に
-- フォールバックして手元に届かない。SSH_TTY に依存せず常に OSC52 を使う。
vim.opt.clipboard = "unnamedplus"
local osc52 = require("vim.ui.clipboard.osc52")
vim.g.clipboard = {
name = "OSC 52",
copy = {
["+"] = osc52.copy("+"),
["*"] = osc52.copy("*"),
},
もうひとつ、コピー側だけ直して安心していると、今度はペーストで固まります。OSC52のペーストは「端末に今のクリップボードを問い合わせる」動作なので、mosh越しだと応答が返らずハングし得る。なので、ペーストはエディタ内のレジスタを返してローカルな貼り付けだけ成立させる、という割り切りを入れています。
-- OSC52 のペースト(端末への問い合わせ)は mosh 越しで応答が返らずハングし得るので、
-- nvim 内のレジスタを返してローカルなペーストだけ成立させる。
paste = {
["+"] = function()
return { vim.fn.split(vim.fn.getreg(""), "\n"), vim.fn.getregtype("") }
end,
},
注意点・Tips
- 「常にOSC52」は万能ではない: ターミナルマルチプレクサを挟むと事情が変わります。実際、あるマルチプレクサ環境では特定バージョンの組み合わせ(zellij 0.44.3 + mosh 1.4.0)でOSC52のコピーが効かず、その層だけは
pbcopyに戻すという判断をしました。「エディタ層はOSC52、マルチプレクサ層はホスト側コピー」と、層ごとに方針が割れることがあります。
// クリップボードは pbcopy 経由。OSC52(copy_clipboard "system")は zellij 0.44.3 +
// mosh 1.4.0 環境では実際のコピーが効かなかったため pbcopy に戻している。
copy_command "pbcopy"
- 切り分けの順番を決めておく: 外から届かないとき、疑う順を「到達性(Tailscale)→ 接続の粘り(mosh)→ シェルのPATH(非対話シェル)→ クリップボード(SSH_TTY/OSC52)」と固定しておくと、迷子になりません。症状の多くは下の層に原因があります。
- 設定に理由をコメントで残す: ここで挙げた落とし穴は、どれも「なぜこう書いたか」を残していなかったら半年後の自分が確実に踏み直すやつです。判断の背景を1行添えておくだけで、未来の自分への回線にもなります。
まとめ
外から手元のマシンへ届く回線は、一枚岩ではなく層で組みます。到達性はTailscaleがCLI1行で担い、接続の粘り強さはmoshが「切れても戻る」で埋め、その先で非対話シェルのPATHとクリップボードのSSH_TTY問題を潰す。派手な仕組みはどこにもなく、地味な穴を一つずつ塞いだだけです。
起動の入口さえあれば外から動かせる、と思って持ち出すと、たいていこの回線側で足止めを食います。手元にAIエージェントを常駐させて外から動かす構成を考えているなら、起動の一手前にあるこの3層を先に組んでおくと、電車のなかの思いつきが素直に手元まで届くようになります。



