ウィンドウを閉じても消えないターミナルをtmuxで用意する

開発未分類メモ

ウィンドウを閉じても消えないターミナルをtmuxで用意する

Windows機からMacで動いているClaude Codeへメッセージを送る仕組みを作った。 仕組みの中身はHTTPのPOSTを1回受けて、その本文をターミナルの入力欄へ貼り、Enterを送るだけである。

この「ターミナルの入力欄へ貼る」を成立させるために、tmuxが要る。 普通のターミナルは、ウィンドウを閉じた瞬間に中のプロセスも道連れになる。 送り先が消えてしまう端末では、送信側は相手が生きているかどうかを常に気にしなければならない。

tmuxを挟むと、端末はウィンドウの寿命から切り離される。 以下は、そのために覚えた最小限の使い方である。

tmuxが何を引き受けているのか

tmuxは常駐するプロセスで、端末そのものを預かる。 ターミナルのウィンドウは、預けてある端末を映すための窓に格下げされる。

この切り離しが効くのは、次の3つの場面である。

ウィンドウを閉じても中身が生き残る:閉じたのは窓であって端末ではない。あとから同じ端末に繋ぎ直せる。

同じ端末を複数の窓から映せる:1つの端末に2つのターミナルウィンドウから同時に接続できる。

窓が1つも開いていなくても端末は動く:メッセージの注入が成立するのはこの性質による。送信側は相手の画面が開いているかを気にしなくてよい。

用語は3階層あるが、当面はいちばん外側だけ覚えれば足りる。

用語実体目安
セッション作業場ひとつ。名前が付く1リポジトリに1つ
ウィンドウセッション内のタブ分けたくなったら足す
ペインウィンドウ内の分割同上

名前が付くのはセッションだけである。 繋ぎ直すときも、外から宛先を指すときも、使うのはこの名前になる。

実際に使うコマンド

新しいセッションを作って中に入る。

tmux new -s eurekapu -c ~/git_repo/eurekapu-nuxt4

-s がセッション名、-c が作業ディレクトリである。 入ったら普通のシェルが出てくるので、そこで目的のコマンドを起動する。

作成と起動をまとめて1行にすることもできる。 最後に渡した文字列がそのまま実行され、-d を付けると画面を切り替えずに裏で作る。

tmux new-session -d -s eurekapu -c ~/git_repo/eurekapu-nuxt4 'claude'

残りは4つで足りる。

tmux ls                          # あるセッションの一覧
tmux a -t eurekapu               # 繋ぎ直す
tmux kill-session -t eurekapu    # 終わらせる
tmux list-clients                # どの窓がどのセッションを映しているか

セッションから離れるときは、Ctrl-b を押してから d を押す。 Ctrl-b はtmuxへの合図で、これを前置きしてから各操作のキーを打つ。 d は detach(切り離し)で、中を生かしたまま窓から出る操作になる。

ウィンドウを Cmd+W で閉じても結果は同じだが、Ctrl-b d のほうが手元の状態がはっきりする。

2つのセッションを並べて見る

tmux switch-client は、1つの窓に映すセッションを差し替えるだけである。 2つを同時に見たいなら、窓のほうを2枚用意して、それぞれ別のセッションに繋ぐ。

macOSのターミナルなら Cmd+N で新規ウィンドウを出し、そこで繋ぎ直す。

tmux a -t eurekapu

どの窓がどこを映しているかは list-clients でわかる。

/dev/ttys000 → claude
/dev/ttys007 → eurekapu

同じセッションに2つの窓から繋ぐと、両方に同じ画面が映り、描画は小さいほうの窓の大きさに合わせて縮む。 並べて別の作業をしたいときは、必ずセッションを分ける。

セッション名がそのまま宛先になる

冒頭のメッセージ注入では、送り先をtmuxのセッション名で指定する。 受け側のサーバーは、渡された名前からペインの識別子を引き、そのペインの状態を確かめてから本文を貼る。

send-to-mac.sh --target eurekapu "本文"

名前が引けなければ404で即座に止まり、相手が応答生成中や確認ダイアログ表示中なら409で拒否される。 届くのは入力欄が空で待っているときだけである。

状態は問い合わせて確認できる。

target: eurekapu   paneId: %5
state: idle
reason: 空の入力ボックスを検出(注入可)

このため、セッション名は場当たりに付けないほうがよい。 リポジトリ名と揃えておけば、送信側は宛先を毎回確認せずに済む。

引っかかった4つの点

tmuxの中でさらに tmux new は打てない。次のように断られる。

sessions should be nested with care, unset $TMUX to force

新しいセッションを作るときは、素のターミナルを1枚出してから打つ。 すでにtmuxの中にいるなら、-d を付けて裏で作れば入れ子にならずに済む。

セッションは放っておくと消えない。これは利点の裏返しで、意識しないと溜まる。 Claude Codeを1セッション動かすとNodeのプロセスが何本も付いてくるので、3つ立てた時点で次のようになっていた。

node系プロセス: 24本 / 合計RSS: 4.7 GB

同じ開発サーバーのポートは取り合う。別のセッションで別のリポジトリを開いても、双方が3000番を使う設定なら衝突する。 セッションを分けたことでポートまで分かれるわけではない。

tmuxはgitに触らない。セッションを作るのはターミナルを1枚用意する操作であって、ブランチの作成でも切り替えでもない。 起動したディレクトリが別のブランチにいたなら、それは前からそうだったということになる。

片付け方

その日もう触らないセッションは落とす。

tmux ls
tmux kill-session -t eurekapu

ただし、メッセージを受け取るためのサーバーを預けているセッションは残す。 これを落とすと注入の口が閉じるので、送信側からは「相手が起きていない」のと区別が付かない状態になる。