ブログ
なぜ Tactic Remote を tmux 上に構築したのか:セッション永続化とターミナルアーキテクチャ
Tactic Remote は tmux を使用して Claude Code セッションを管理し、ネットワーク切断、Mac のスリープサイクル、アプリの再起動にまたがる永続性を提供しながら、ターミナル出力を iPhone にリアルタイムでストリーミングします。
このカテゴリのプロダクト/開発アップデートを確認できます。
カテゴリを見るTactic Remote の構築を始めたとき、最初に直面したアーキテクチャの決定は根本的なものでした:制御デバイス(iPhone)の接続が不安定な場合、Claude Code セッションをどのように維持し、観測可能にするか?答えは tmux でした。そしてこの決定が製品のほぼすべてを形作りました。
問題
Claude Code は長時間動作するインタラクティブなプロセスとして実行されます。典型的なセッションは 10 分から 60 分続きます。この間、Claude はファイルを読み、変更を提案し、コマンドを実行し、承認を待ち、フィードバックに基づいてイテレーションします。セッションの途中でプロセスが終了すると、すべてのコンテキストが失われます。Claude は最初からやり直し、ファイルを再読み込みし、タスクの理解を再構築する必要があります。
ローカルターミナルでは問題になりません——Mac は起動し続け、プロセスは完了まで実行されます。しかし Tactic Remote はモバイルクライアントを導入します。iPhone は Wi-Fi とセルラーを切り替えます。画面をロックし、バックグラウンドアプリをサスペンドします。ユーザーは圏外の場所を通過します。Claude Code のプロセスライフサイクルを iPhone のネットワーク接続に紐づけるアーキテクチャは、根本的に破綻しています。
Claude Code がクライアント接続とは独立して実行され、クライアントが実行中のセッションに影響を与えずにアタッチ・デタッチできる必要がありました。
なぜ tmux なのか
tmux は 2007 年以来本番環境で使用されているターミナルマルチプレクサです。ターミナルセッションの概念を、それを表示するターミナルエミュレータから分離します。tmux セッションはホストマシン上のサーバープロセスとして動作します。クライアントはセッションにアタッチして表示・操作し、デタッチしても実行中のプロセスには影響しません。
tmux に決定する前に、いくつかの代替案を評価しました:
screen — 元祖ターミナルマルチプレクサ。機能的ですがメンテナンスが活発でなく、スクリプト API もより限定的です。tmux の構造化されたコマンドインターフェースにより、プログラム的な制御が大幅に容易になりました。
カスタム PTY 管理 — node-pty を使用した生の疑似端末によるソリューションをプロトタイプしました。基本的なケースでは動作しましたが、セッション永続化、ウィンドウ管理、出力バッファリングを再実装する必要がありました。劣化版の tmux をスクラッチで構築していたのです。
コンテナベースの分離 — PTY を接続した Docker コンテナで Claude Code を実行する方法。これはオーバーヘッドを追加し、ファイルシステムアクセスを複雑にし(Claude Code は実際のプロジェクトファイルの読み書きが必要)、解決するよりも多くの問題を生む抽象化レイヤーを導入します。
nohup / disown — 単純なバックグラウンドプロセス管理。出力キャプチャなし、再アタッチなし、プログラム的な制御なし。インタラクティブセッションには不十分です。
tmux は必要なものをすべて標準で提供してくれました:セッション永続化、プログラム的なペイン制御、出力キャプチャ、安定して十分に文書化された API。トレードオフは開発者の Mac に tmux をインストールする必要があることですが、brew install tmux は一度きりのセットアップコストであり、許容範囲と考えています。
アーキテクチャ
Tactic Remote の tmux 統合は 3 つのレイヤーで動作します:
レイヤー 1:セッション管理
Tactic Remote を通じて Claude Code セッションを開始すると、Mac コンパニオンは一意の識別子を持つ新しい tmux セッションを作成します。Claude Code はそのセッションのプライマリペインで起動します。
-d フラグが重要です——デタッチモードでセッションを作成します。セッションが存在し実行するためにターミナルエミュレータを接続する必要はありません。Claude Code は即座に起動し、Mac コンパニオンがバックグラウンドで監視します。
各セッションは共有セッション内のペインではなく、専用の tmux セッションを取得します。これにより、セッション固有の環境変数、独立したスクロールバッファ、クロスセッション干渉のリスクなしという、クリーンな分離が提供されます。
レイヤー 2:出力キャプチャとストリーミング
Mac コンパニオンは tmux ペインからのターミナル出力を継続的にキャプチャし、WebSocket 経由で接続された iPhone クライアントにストリーミングします。これが iPhone でリアルタイムに Claude Code の出力を確認できるメカニズムです。
出力キャプチャは tmux capture-pane を使用し、表示可能なペインコンテンツとスクロールバッファをカバーする座標を指定します。コンパニオンはキャプチャループを実行し、以下を行います:
- 設定可能な間隔(デフォルト:200ms)で現在のペインコンテンツをキャプチャします。
- キャプチャしたコンテンツを最後の既知の状態と差分比較します。
- 接続されたクライアントにデルタのみを送信します。
- 再接続して追いつく必要があるクライアントのために、最近の出力のリングバッファを維持します。
200ms のキャプチャ間隔は、応答性と CPU 使用率のバランスを表しています。このレートでは、ターミナル更新は iPhone 上でほぼリアルタイムに感じられ、最新の Mac で 2% 未満の CPU を消費します。より速い更新を好むユーザーは間隔を 100ms に短縮でき、バッテリー駆動のユーザーは 500ms に延長できます。
レイヤー 3:入力ルーティング
iPhone から入力を送信すると——承認レスポンス、入力メッセージ、セッション制御コマンドのいずれであっても——Mac コンパニオンは tmux send-keys を使用して適切な tmux ペインにルーティングします。これにより、実際の入力が WebSocket を介してネットワークを越えて tmux セッションに到達する一方で、直接ターミナル接続のイリュージョンが維持されます。
承認レスポンスは特殊なケースです。生のキーストロークとして送信されるのではなく、hook システムによって処理され、Claude Code の権限システムが期待する構造化されたレスポンスとして注入されます。これにより、承認はアトミックであり、部分的なネットワーク配信によって破損することはありません。
実際の永続化
tmux アーキテクチャは複数のレベルで永続化を提供します:
iPhone アプリが閉じた場合。 tmux セッションは実行を継続します。Claude Code は動作を続け、承認は Mac コンパニオンでキューに入ります。iPhone アプリを再度開くと、WebSocket に再接続し、バッファされた出力を受信し、元の状態に戻ります。
iPhone がネットワークを失った場合。 セッションの観点からはアプリが閉じた場合と同一です。WebSocket が切断され、出力は Mac でバッファされ、再接続で状態が復元されます。
Mac がスリープした場合。 tmux セッションはスリープとウェイクのサイクルを生き延びます。Mac がウェイクすると、tmux サーバープロセスが再開し、実行途中だった Claude Code プロセスが続行します。Mac コンパニオンは WebSocket リスナーを再確立し、iPhone に再接続します。
Mac コンパニオンがクラッシュした場合。 tmux セッションは独立して生存します。Mac コンパニオンを再起動すると、claude-remote- プレフィックスを持つ既存の tmux セッションを検出し、再アタッチしてストリーミングを再開します。Claude Code のコンテキストは失われません。
Mac が再起動した場合。 これは tmux セッションが生存できない唯一のシナリオです。tmux セッションはメモリ上にのみ存在するためです。Claude Code を再起動する必要があります。これは明確に文書化しており、将来のバージョンに向けて tmux-resurrect の統合を検討していますが、インタラクティブな AI セッション(単なるシェル状態ではなく)の復元の複雑さにより、これは簡単ではありません。
セッションライフサイクル
Tactic Remote セッションは以下の状態を経ます:
| 状態 | tmux ステータス | iPhone ステータス |
|---|---|---|
| 起動中 | セッション作成済み、Claude Code 起動中 | 読み込みスピナー |
| アクティブ | プロセス実行中、出力フロー中 | ライブターミナルビュー |
| 承認待ち | プロセスが hook でブロック中 | 承認通知 |
| 一時停止(クライアント切断) | 実行中、出力バッファリング中 | 未接続 |
| 完了 | プロセス終了、セッション保持中 | セッションサマリー |
| 期限切れ | 保持期間後にセッション破棄 | アーカイブ済み |
完了したセッションは設定可能な期間(デフォルト:30 分)tmux に保持されるため、Claude Code が終了した後でも最終出力を確認できます。保持期間後、tmux セッションは破棄され、出力ログはアーカイブされます。
可観測性
tmux は、カスタム PTY ソリューションでは実現が困難なイントロスペクション機能を提供します:
- セッション一覧 —
tmux list-sessionsがすべてのアクティブな Tactic Remote セッションの即時インベントリを提供します。 - ペインの寸法 — iPhone のディスプレイ特性に合わせてペインの寸法をクエリおよび設定でき、出力の折り返しが正確になります。
- プロセス監視 —
tmux list-panesにフォーマットフラグを指定することで、個別の PID 追跡システムを維持せずに Claude Code プロセスを監視できます。 - スクロールバッファアクセス — 完全なスクロールバッファが検索と取得に利用可能で、iPhone のセッション履歴ビューを支えています。
パフォーマンス特性
tmux を介した実行は、直接ターミナルと比較して最小限のオーバーヘッドを追加します:
| メトリクス | 直接ターミナル | tmux セッション |
|---|---|---|
| プロセス起動 | ~800ms | ~850ms |
| キーストロークレイテンシ | <1ms | ~2ms |
| セッションあたりのメモリオーバーヘッド | — | ~3MB |
| CPU オーバーヘッド(アイドルセッション) | — | <0.1% |
オーバーヘッドは実際には計測不能です。tmux は約 20 年近くの本番使用を通じて最適化されており、導入される追加の間接層はユーザーには見えません。
得られた教訓
tmux 上に構築するのは正しい判断でしたが、課題がなかったわけではありません:
ターミナルエンコーディングのエッジケース。 Claude Code の出力には、tmux のターミナルエミュレーションと予期しない相互作用を起こす ANSI エスケープシーケンスが含まれることがあります。iPhone にストリーミングする前に出力をクリーンアップする正規化レイヤーを維持しています。
macOS での tmux インストール。 tmux は macOS にプリインストールされていません。バンドルすることを検討しましたが、やめることにしました——開発者は一般的に自分で tmux のインストールを管理することを好み、バンドルすると既に tmux をインストールしているユーザーとバージョンの競合が発生します。
大規模での capture-pane のパフォーマンス。 多数の同時セッションでは、キャプチャループの CPU 使用量が線形にスケールします。最近の出力変更がないセッションの頻度を下げる適応型キャプチャレートを実装しました。
tmux はうまく機能しているときに存在を消すインフラストラクチャです。ほとんどの Tactic Remote ユーザーは tmux が関与していることを知らないし、気にもしません——彼らが見ているのは、電話やネットワークがどうなっても信頼性のあるセッションです。それが私たちが目指していた結果です。
Tactic Remote を試す
スマートフォンからコーディングエージェントを操作
Mac、Windows、Linux 上の Claude Code や Codex などに接続。iPhone や iPad で進捗を確認し、次の指示を送れます。