← Vim研究所

Neovim 0.13の:detach! SSHが切れても作業が消えない

2026-08-23 公開

SSH越しにサーバのファイルを編集している最中に回線が切れると、向こう側のNeovimはそこで終わります。開いていたバッファも、まだ保存していない編集も一緒に消えます。これを避けるために「まずtmuxを起動してからNeovimを立ち上げる」という順番を体に覚えさせている人は多いはずです。Neovim 0.13の:detach!は、この備えをNeovim自身に持たせるコマンドです。

検証環境:nightlyビルドのNVIM v0.13.0-dev-1389+gd3b4f562a6と安定版のNVIM v0.12.4(macOS)を使い、tmuxで作った端末の中でNeovimを起動して、端末ごと落とす操作を繰り返しました。掲載しているプロセス数・エラーメッセージ・serverlist()の出力はすべて実行結果です。長いパスとユーザー名だけ読みやすさのために伏せてあります。0.13は開発中なので、リリースまでに細部が変わる可能性があります。

目次(9項目)
操作これだけ覚える
:detach!印を付けるだけ。端末が落ちても本体が残る
:detachいますぐ切り離す(0.12.4にもある)
:%detach自分以外のUIを切り離す
:connect {アドレス}残っている本体に乗り移る
nvim --server {アドレス} --remote-ui端末からつなぎ直す
serverlist(#{info: v:true})残っている本体を探す

端末が落ちるとNeovimも道連れになる

まず何が起きているかを測りました。tmuxで端末を1つ作り、その中でNeovimを起動して1行目にWIP-と書き足します。保存はしません。そのうえでtmuxのセッションごと落とします。回線が切れて端末が消えるのと同じ状況です。数えているのはNeovimのプロセス数です。

                            端末を落とす前   端末を落とした後
0.13  :detach! を打たない        2 個            0 個
0.13  :detach! を打つ            2 個            1 個
0.12.4(:detach! は使えない)     2 個            0 個

打たなかった場合は2つとも消えました。未保存のWIP-もろとも失われます。:detach!を打っておいた場合だけ1つ残ります。0.12.4で同じコマンドを打つと、その場で断られました。

E477: No ! allowed

事故に備えて印を付けるだけのコマンド

紛らわしいのですが、:detachそのものは0.12.4にもあります。ただしこちらは打った瞬間にUIを切り離すコマンドで、試すとNeovimの画面が閉じてシェルに戻り、本体だけが残りました。SSHが切れるのは予期しないから事故なのであって、切れる前に:detachを打てるなら困っていません。0.12.4のヘルプはui-lifecycleの使いどころとして「予期しないSSH切断のあともセッションを生かす」を挙げていますが、その用途を素の:detachだけで満たすことはできませんでした。

0.13の:detach!は、いま切り離すのではなく「あとでUIが不意に消えても終了するな」という印を付けます。打つと次のメッセージが出て、画面はそのまま編集を続けられます。

Nvim will continue running if the UI disconnects

印を付けるだけなので、起動して最初に1回打っておけば足ります。毎回打つのが面倒なら設定ファイルに書けます。手元ではinit.luaにこの1行だけを置いた状態で起動し、端末を落としても本体が残ることを確認しました。

-- init.lua に書く場合
vim.cmd('silent detach!')
" init.vim に書く場合
silent detach!

なぜ端末と一緒に死ぬのか

仕組みを見ると腑に落ちます。NeovimをTUIとして起動すると、プロセスは1つではなく2つ立ちます。psでPIDと親PIDを並べたのが次の出力です。

6005  6003 nvim --embed -u NONE -i NONE -n --listen /tmp/nvp.sock p.txt
6003 10259 nvim -u NONE -i NONE -n --listen /tmp/nvp.sock p.txt

左がPID、その右が親のPIDです。下の6003が端末につながっているUIで、上の6005はその子として動いている本体です。バッファも未保存の変更も持っているのは子のほうで、UIは画面を描いてキーを送るだけの薄い層になっています。端末が消えるとUIが死に、UIが死ぬと本体も後を追います。:detach!が印を付ける相手は本体の側です。

この見方をすると、印は1つのNeovimにつき1回でよいことも説明が付きます。:detach!を打ってから端末を落とし、別の端末からつなぎ直し、そのつなぎ直したほうを今度は:detach!を打たずに落としてみましたが、本体は残ったままでした。あとからソケット越しにつないだUIは本体の子ではないので、道連れにする関係がそもそも無いということです。

落ちたあとにつなぎ直す

残っている本体には住所があります。v:servernameがそれで、--listenを付けずに起動しても自動で1つ割り当てられます。

/var/folders/…/nvim.<ユーザー名>/12xxuk/nvim.1242.0

控えてあれば、別の端末からこれを指してつなぎ直せます。

# 端末からつなぎ直す
nvim --server /var/folders/…/nvim.1242.0 --remote-ui

問題は、事故のときに限って住所を控えていないことです。0.13のserverlist()infoを付けると、自分が開いたサーバだけでなく同じマシンで動いている他のNeovimも返すようになりました。適当にNeovimを起動して次を打つと、残骸が見つかります。

:echo serverlist(#{info: v:true})
[{'active': 1787463621942746880, 'pid': 1518,
  'addr': '/var/folders/…/FGupQ5/nvim.1518.0', 'own': v:true},
 {'active': 1787463613678382080, 'pid': 1242,
  'addr': '/var/folders/…/12xxuk/nvim.1242.0', 'own': v:false}]

ownv:falseのほうが、端末を失って残っている本体です。addrをそのまま:connectに渡すと、いま開いているUIがそちらへ乗り移ります。手元では、消えたはずのWIP-付きのバッファがそのまま出てきました。

:connect /var/folders/…/12xxuk/nvim.1242.0

つなぎ直すときの端末は、元と同じ大きさでなくても構いません。100桁24行で作業していたものに60桁18行の端末からつないだところ、画面は新しい大きさに合わせて描き直されました。ひとつだけ癖があって、つないだ直後は画面が真っ白のままのことがあります。何かキーを押すと描画されるので、失敗したと思って閉じないほうがよいです。

:terminalの中のシェルも生きている

残るのはバッファだけではありません。:terminalで開いたシェルごと残ります。Neovimの中でシェルを開き、sleep 600 &をバックグラウンドに投げてから端末を落としてみました。sleepのプロセスは生きたままで、つなぎ直したあとの画面には落とす前のやりとりがそのまま残り、同じシェルが続けて入力を受け付けました。

katsushi@host ~ % sleep 600 & echo MARKER-ALIVE
[1] 3821
MARKER-ALIVE
katsushi@host ~ % echo RECONNECTED
RECONNECTED

上の3行が端末を落とす前、下の2行がつなぎ直したあとのやりとりです。落とす前の出力とジョブ番号がそのまま画面に残っていて、sleepのプロセスも生きたままでした。シェルが起動し直されたわけではありません。ここは:restartによる再起動との分かれ目で、あちらはウィンドウ配置とバッファを復元しますが、ターミナルバッファで動いていたプロセスは戻りません。:detach!は本体を落とさないので、動いていたものが動いたままになります。

tmuxとの違いと使い分け

やっていることが似ているので並べます。守れる範囲が違います。

項目tmux:detach!
守れる範囲端末の中で動いているものすべてそのNeovim1つ(中のシェルを含む)
事前の手当て先にtmuxを起動しておくNeovimの中で1回打つ
つなぎ直し方tmux attach--remote-ui:connect
Neovim以外守れる守れない

ビルドやテストを別のペインで流しているなら、その面倒はtmuxしか見られません。逆にssh host nvim fileのようにNeovimだけを直接起動する使い方だと、tmuxを挟むために手順が1つ増えていたところが:detach!で済みます。VPSにSSHで入って編集する構成では、どちらを選んでもよく、両方かけておいても損はありません。tmuxを使っていても、そのtmuxサーバごと落ちる事故には:detach!のほうが効きます。

気をつけること

いちばん引っかかるのは、つなぎ直さないと本体が残り続けることです。事故のあとに新しくNeovimを立ち上げて作業を再開すると、古い本体が未保存の変更を抱えたままメモリに残ります。serverlist()で見つけて:connectし、要らなければ:qaで閉じます。乗り移ると同時に元のほうを止める:connect!もあります。

UIが複数ぶら下がっている状態も作れます。同じ本体に2つの端末からつないだ状態で:%detachを打つと、自分以外が切り離されました。実測ではnvim_list_uis()の数が2から1に減り、もう一方の端末は閉じています。誰かに画面を共有していたのを片付けるときの操作です。

:detach:detach!は名前が1文字違うだけで役割が逆に近いので、そこだけ混ぜないようにします。前者はいま切り離す、後者は将来に備えて印を付ける、です。そして0.13はまだ開発中です。手元で確かめたのは開発版なので、リリースまでに動きが変わる可能性は残ります。

使ってみて

最初は「tmuxがあるのだから要らないのでは」と思っていました。考えが変わったのは、印を付けたあとの本体が:terminalの中のシェルごと残ると分かったときです。手元の検証ではsleepを投げたシェルがジョブ番号もそのままで戻ってきました。もうひとつ、住所を控えていない状態からserverlist()で残骸を見つけられたのが大きく、これが無いと「生きているのは分かるがつなぎ方が分からない」で詰みます。ひとつ焦ったのは、つなぎ直した直後に画面が真っ白だったことで、失敗したと思って一度閉じかけました。キーを1つ押せば出てきます。

まとめ

Neovim 0.13の:detach!は、端末が不意に消えてもNeovimの本体を終了させないための印です。実測では、打たずに端末を落とすとプロセスは2つとも消え、打っておくと1つ残りました。0.12.4には:detachしか無く、こちらは打った瞬間に切り離すコマンドなので、予期しない切断には間に合いません。残った本体にはv:servernameの住所があり、控えていなくてもserverlist(#{info: v:true})ownv:falseのものを探せます。あとは--remote-ui:connectでつなぎ直すと、未保存の変更も:terminalのシェルもそのまま戻ってきます。tmuxが端末全体を守るのに対してこちらはNeovim1つぶんですが、起動して1回打つだけで済むので、SSHの先で編集する日には入れておく価値があります。

関連記事

← Vim研究所 トップへ戻る