HN 日本語サマリー

← 一覧へ戻る
プログラミング

TmuxとClaude Code: セッション、Send-Keys、そして天井

Tmux and Claude Code: Sessions, Send-Keys, and the Ceiling (crystl.dev)

130 pointsby ankitg1291 コメント

要約

この記事は、AIコーディングエージェントを管理するためのツールとしてtmuxの活用法を解説しています。tmuxは、セッションの永続性、デタッチ機能、複数エージェントの管理、スクリプトからの操作性といった利点を提供します。Claude Codeの最新バージョンでは、`claude --worktree --tmux` コマンドでtmuxセッションとワークツリーを自動生成する機能が追加されましたが、これは単一エージェント向けであり、より高度な設定には手動でのtmux設定が必要になる場合があることを説明しています。

全文翻訳

TmuxとClaude Code:セッション、Send-Keys、そして限界

tmuxは、AIコーディングエージェントを名前付きで分離可能なセッションで実行し、ターミナルを閉じたりSSH接続が切断されたりしても持続させます。1つのウィンドウに多数のエージェントを配置し、スクリプトから任意のセッションにキー入力を送信したり、画面の内容を読み取ったりできます。この組み合わせが、人々がtmuxを使用する理由です。

これらの機能を自作する前に、最近のClaude Codeがclaude --worktree --tmuxコマンドでワークツリーとtmuxセッションを自動作成してくれる組み込みパスを確認してください。この記事ではまずその機能について説明し、次にそれがカバーしない範囲、そしてtmux自体の限界について解説します。

なぜtmuxとエージェントは相性が良いのか

コーディングエージェントは、ターミナルと継続的に対話する必要がある長時間実行プロセスです。tmuxの以下の3つの特性が、その要件に合致しています。

切断からの復旧: tmuxサーバーがpty(疑似端末)を所有するため、ターミナルウィンドウが切断されてもエージェントは動作し続けます。ウィンドウを閉じたり、SSHリンクが失われたり、ラップトップをスリープさせたりしても、エージェントは作業を継続し、再接続時にそのまま利用できます。

1つのウィンドウに多数のセッションを保持: 4つのエージェントは、アプリケーションスイッチャーで探す4つのウィンドウではなく、キー操作で切り替えられる4つの名前付きセッションになります。

外部からのスクリプト操作が可能: どのシェルからでも、実行中のエージェントにキー入力を送信したり、その画面を読み取ったりできます。IPC(プロセス間通信)を構築したり、APIを連携させたりする必要はありません。この機能が、人々がエージェントオーケストレーションスクリプトにtmuxを組み込む理由です。

何も構築する前に、組み込みフラグを確認

Claude Codeには、単一エージェントがブランチ上で動作するケースのために、この機能が組み込まれています。最近のバージョンでは、claude --helpで以下の2つのフラグを確認できます。

-w, --worktree [name]   セッションのために新しいgit worktreeを作成します
                        (オプションで名前を指定できます)
    --tmux              ワークツリーのためにtmuxセッションを作成します
                        (--worktreeが必要です)。利用可能な場合はiTerm2ネイティブペインを使用します。
                        従来のtmuxを使用する場合は --tmux=classic を使用してください。

したがって、「このタスクを分離し、独自のセッションを与える」という一連の操作は、1つのコマンドで実行できます。

claude --worktree --tmux

# またはワークツリーに名前を付ける
claude --worktree auth-refactor --tmux

この機能に頼る前に、2つの詳細に注意してください。--tmuxは--worktreeなしではエラーになるため、これはセッションを作成するワークツリーの機能であり、一般的なセッションフラグではありません。また、iTerm2ではtmuxの代わりにネイティブペインを使用するため、見た目は良いですが、このページ後半のtmuxコマンドはアタッチするものがなくなります。実際のtmuxを下に置きたい場合(セッションに対してスクリプトを作成する場合)は、--tmux=classicを渡してください。

単一ブランチ上の単一エージェントの場合、これが答えです。この機能を使用し、先に進んでください。

この機能ができないこと:1つのセッションで複数のエージェントを実行する、それらを表示するためにペインを配置する、スクリプトから実行中のエージェントにタイプ入力する、どのエージェントがあなたを待っているかを示す、SSH経由でリモートボックスにこれらのいずれかをセットアップする。これらはこの記事の後半で扱い、人々がtmuxを手動で記述し始める場所です。

フラグのワークツリー部分は、git worktrees for parallel developmentでさらに詳しく説明しています。

プロジェクトごとのセッション、プロジェクト名で命名

これを使いやすく保つための整理ルール:リポジトリごとに1つのtmuxセッションを作成し、リポジトリ名で命名します。タスクごと、エージェントごとではありません。タスクは出入りしますが、プロジェクトは安定したものです。

# プロジェクトディレクトリでデタッチされたセッションを開始
tmux new-session -d -s api -c ~/code/api

# それにアタッチする(存在しない場合は作成します)
tmux new-session -A -s api -c ~/code/api

-Aはアタッチまたは作成です。これにより、シェル関数で実行したい繰り返し実行しても安全な単一コマンドになります。

# ~/.zshrc
agent() {
  local dir="${1:-$PWD}"
  local name="$(basename "$dir" | tr . -)"
  tmux new-session -A -s "$name" -c "$dir"
}

セッション名には.または:を含めることはできません(tmuxはそれらをターゲット区切り文字として使用するため)、そのためtrを使用しています。

その後、tmux lsは実際のインベントリになります。

$ tmux ls
api: 3 windows (created Fri Jul 31 09:12:04 2026)
web: 2 windows (created Fri Jul 31 09:40:11 2026)
infra: 1 windows (created Fri Jul 31 11:02:55 2026)

フォーマット文字列でさらに情報を追加できます。

tmux list-sessions -F '#{session_name}  windows=#{session_windows}  attached=#{session_attached}'

複数のエージェントを監視するためのレイアウト

セッション内で、各エージェントにタスク名でウィンドウを割り当てます。

tmux new-window -t api -n auth   -c ~/code/api
tmux new-window -t api -n tests  -c ~/code/api

ウィンドウは、アクティブに監視していないエージェントにとってはペインよりも優れています。なぜなら、ウィンドウは画面スペースを消費しないからです。ペインは、現在並べて表示したい2つか3つのエージェント用です。

# 現在のウィンドウを分割し、その中のものをタイル状に配置
tmux split-window -h -c '#{pane_current_path}'
tmux split-window -v -c '#{pane_current_path}'
tmux select-layout tiled

#{pane_current_path}は省略しやすいです。これがないと、新しいペインはセッションが開始されたディレクトリではなく、実際にいるディレクトリで開かれ、そこで起動するエージェントは間違ったリポジトリを指します。

名前付きのレイアウトは2つあります:エージェントの均一なグリッドにはtiled、1つの大きなペイン(操作するエージェント)とその横に積み重ねられた残りのペインにはmain-verticalです。

tmux select-layout main-vertical
tmux set-window-option main-pane-width 60%

main-pane-widthのパーセンテージ値はtmux 3.2以降が必要です。古いビルドでは絶対的な列数(main-pane-width 120)を使用します。tmux -Vで確認してください。

持っておく価値のある.tmux.conf

これらは、特にエージェントの作業に関連する行です。これらは完全な設定ではなく、多数のエージェントを使用する際にデフォルトから変更したい部分です。

# エージェントは大量の出力を生成するため、スクロールバックして確認したいでしょう
set -g history-limit 100000

# 24ビットカラー、エージェントのTUIが設計通りにレンダリングされるように
set -g default-terminal "tmux-256color"
set -ga terminal-overrides ",*256col*:Tc"

# マウスでペイン間をスクロールおよびクリック
set -g mouse on

# 実行中のプロセスが勝手にウィンドウ名を変更するのを防ぐ
set -g allow-rename off
set-window-option -g automatic-rename off

# 1から始まるウィンドウはキーボードの数字キーと一致させます
set -g base-index 1
set-window-option -g pane-base-index 1
set -g renumber-windows on

# 他の場所にいる間にエージェントが出力を生成したウィンドウをフラグ付けします
set-window-option -g monitor-activity on
set -g visual-activity off

# ステータスラインはモニターとして十分な頻度でリフレッシュします
set -g status-interval 5

# エージェントのTUIがエスケープシーケンスを読み取る際の遅延なし
set -sg escape-time 10

# プレフィックスキーなしでペインをジャンプ
bind -n M-h select-pane -L
bind -n M-j select-pane -D
bind -n M-k select-pane -U
bind -n M-l select-pane -R

# サーバーを再起動せずにリロード
bind r source-file ~/.tmux.conf \; display-message "config reloaded"

エージェントのTUIはターミナルタイトルを設定したがるため、allow-rename offがないと、注意深く名前を付けたauthウィンドウがエージェントが付けた名前に変わってしまいます。

escape-time 10は、「エージェント内の矢印キーが遅い」という不満を解消します。デフォルトの遅延は、実際のEscapeキーとエスケープシーケンスを区別するために存在し、インタラクティブなTUIを鈍く感じさせます。

send-keysでエージェントを操作

send-keysは、キーボードにいるかのようにペインにタイプ入力します。cron、gitフック、または別のエージェントからエージェントをスクリプト化する方法はこれです。

# ターゲット構文: session:window.pane
tmux send-keys -t api:auth "run the test suite and fix what fails" Enter

ここで3つの問題が発生します。発生頻度の順です。

ベア引数はキー名として解釈されます。 Enter、C-c、Escape、Spaceはテキストではなくキーです。プロンプトにこれらの単語が単独の引数として含まれている場合、キープレスとして送信されます。リテラル文字列を送信するには-lを使用してください。

tmux send-keys -t api:auth -l "press Enter to continue was the old flow"
tmux send-keys -t api:auth Enter

-lはキー名ルックアップを完全に無効にするため、改行は2回目の呼び出しで行う必要があります。

セミコロンはtmuxコマンドを終了させます。 テキスト内の末尾の;はコマンド区切り文字として読み取られます。\;としてエスケープしてください。

エージェントのTUIは、テキストと改行の間に間隔が必要です。 多くのエージェントの入力ボックスは、ペースト時にデバウンスまたは再レンダリングし、同じフレームでEnterが到着すると無視されます。信頼性の高いパターンは次のとおりです。

tmux send-keys -t api:auth -l "$PROMPT"
sleep 0.3
tmux send-keys -t api:auth Enter

暴走しているエージェントを中断するのも同じメカニズムです。

tmux send-keys -t api:auth C-c

capture-paneでエージェントを読み取る

capture-paneはペインの内容を標準出力にダンプします。これはペアの読み取り側です。

# 表示されている画面
tmux capture-pane -p -t api:auth

# 最後の200行、折り返された行は結合されたまま
tmux capture-pane -p -J -S -200 -t api:auth

# 全てのスクロールバック
tmux capture-pane -p -J -S - -t api:auth

# カラーエスケープシーケンスを保持
tmux capture-pane -p -e -t api:auth

-Jは見た目以上に重要です。これがないと、ターミナル幅で折り返された行は2行として返され、出力に対して書くgrepはすべてその境界で間違った結果になります。

スナップショットではなく連続ログの場合は、pipe-paneがペインが生成するすべてをコマンドにストリームします。

tmux pipe-pane -o -t api:auth 'cat >> ~/agent-logs/api-auth.log'

# 停止する
tmux pipe-pane -t api:auth

-oはトグルなので、2回実行するとオフになります。出力は生のまま(エスケープシーケンスを含む)なので、後で読みやすくしたい場合はsed -E 's/\\[0-9;]*[a-zA-Z]//g'のようなものでパイプ処理してください。

ステータスラインモニター

tmuxで複数のエージェントを実行する人は、最終的にこれを作成します。どのエージェントが動作中で、どれが待機中かを示すステータスラインです。

メカニズムはステータスラインの#()で、シェルコマンドを実行し、その出力をstatus-interval秒ごとにリフレッシュして補間します。

set -g status-interval 5
set -g status-right '#(~/bin/tmux-agent-status) | %H:%M'
set -g status-right-length 100

そして、各ペインの活動の兆候をキャプチャースクレイピングするスクリプトです。

#!/usr/bin/env bash
# ~/bin/tmux-agent-status
out=""
while read -r target; do
  screen="$(tmux capture-pane -p -J -S -8 -t "$target" 2>/dev/null)"
  name="${target%%:*}"
  case "$screen" in
    *"Do you want"*|*"❯ 1."*) out+="⏸ $name " ;;   # プロンプトで一時停止中
    *"esc to interrupt"*)      out+="● $name " ;;   # 作業中
    *)                         out+="○ $name " ;;   # アイドル
  esac
done < <(tmux list-panes -a -F '#{session_name}:#{window_index}.#{pane_index}')
printf '%s' "$out"

これは機能し、構築する価値があります。それが何をしているのかを明確に理解してください:5秒ごとのポーリングで、開いているすべてのペインに対して、TUIのレンダリングフレーム(スピナー、ボックス描画、切り捨てられた列、ANSI)を文字列マッチングしています。マッチ文字列は、エージェントのバージョンが今週印刷するものになります。

より軽量なバージョンは、スクレイピングの代わりにtmux自体の活動追跡を使用します。monitor-silenceは、ペインがN秒間出力しない場合にアラートを発火させます。これは「エージェントが停止した」という最も近いネイティブ信号です。

set-window-option -g monitor-silence 30
set -g visual-silence on

これにアクションをフックできます。

set-hook -g alert-silence 'run-shell "osascript -e \"display notification \\\"agent idle\\\"\""'

沈黙は完了の代理であり、完了の報告ではありません。エージェントが大きなファイルを処理するために考えている場合も沈黙します。

デタッチ、再アタッチ、そして何が何を持続するか

tmux detach-client            # またはプレフィックス、次に d
tmux attach -t api
tmux switch-client -t web     # 他のセッション内から
tmux kill-session -t api

持続するもの:

イベントエージェントは実行を続けますか?
ターミナルウィンドウを閉じるはい
クライアントをデタッチするはい
SSH接続が切断されるはい
tmux kill-sessionいいえ
tmux kill-serverいいえ
再起動いいえ
macOSの再起動後の「ウィンドウを再オープン」いいえ

tmuxサーバーは単なるユーザープロセスです。tmuxは再起動を乗り越えるものではなく、セッションのスクロールバックはサーバーが停止すると失われます。トランスクリプトが重要であれば、pipe-paneでファイルに保存するか、履歴を独自に永続化するツールを使用してください。

人々がつまずく一つの詳細:デタッチされたセッションのペインは、最後にアタッチされたクライアントのサイズ、または一度もアタッチされなかった場合は80x24にサイズ設定されます。離れている間に80列のペインにレンダリングされるエージェントTUIは、200列で再アタッチするときに、再描画されるまで正しく表示されません。複数のクライアントが異なるサイズでアタッチされている場合、set-window-option -g aggressive-resize onが役立ちます。

SSH経由のtmux

これはtmuxが本当に置き換えにくい場所です。リモートボックスで、信頼できないリンク経由でエージェントを実行する場合:

# アタッチまたは作成、1つのコマンドで、ttyを割り当てて
ssh devbox -t tmux new-session -A -s agents

タスクの途中で接続が切断され、同じコマンドを再度実行すると、同じセッションに戻り、エージェントはまだ実行中です。リンクがひどく不安定でSSH自体が頻繁に切断される場合は、moshとペアにしてください。

ネストされたtmuxの問題:ローカルでtmuxを実行し、リモートボックスでも実行すると、両方とも同じプレフィックスをキャプチャします。2つの解決策は、リモートで異なるプレフィックスを使用すること(set -g prefix C-a)、またはプレフィックスを2回押すことです。これはbind C-b send-prefixで提供されます。

目の前にあるマシンではないマシンでエージェントを実行するには、remote developmentを参照してください。

tmuxが限界を迎える場所

tmuxは優れたソフトウェアです。多重化と永続化を、それらが解決できる限りうまく解決しており、2007年からそれを続けています。それができないのは、エージェントに特化したことではありません。なぜなら、tmuxのモデルが設計されたときにはエージェントは存在しなかったからです。

どのエージェントが一時停止しているかの可視性がない。 承認プロンプトを待っているエージェントは、考えているエージェントとまったく同じように見え、20分前に終了したエージェントとも同じように見えます。これらすべてが「テキストが表示されているペイン」です。上記のステータスラインハックは、これを隠すために存在し、スクレイピングによって隠しています。

完了時の通知がない。 monitor-silenceが最も近いものであり、あらゆる理由で沈黙しているペインで発火します。エージェントが何であるかをtmuxは知らないため、「エージェントが完了した」というイベントはありません。

画面スクレイピングは構造的に壊れやすい。 capture-paneはレンダリングされたフレームを提供します:スピナー、ボックス描画、切り捨てられた列、ANSI。エージェントが何を言ったか、どのツールを呼び出したか、その呼び出しが成功したかではありません。それに対して書くすべてのパーサーは、エージェントがUIを変更すると壊れます。

セッション間の共有状態がない。 2つのtmuxセッションにある2つのエージェントは、互いを見る方法がありません。一方が他方のトランスクリプトを読み取ったり、作業を引き継いだりしたい場合、ファイル、ロック、ポーリングから自分で構築する必要があります。

複数のストリーミングエージェントのスクロールバックはコストがかかる。 12個のエージェント出力ペインにわたるhistory-limit 100000は、サーバーが存在する限りペインごとに保持される実際のメモリであり、サーバーが停止した瞬間に失われます。このレンダリング側のコストについても、terminal emulators for AI agentsを参照してください。

再起動を乗り越えるものはない。 すべてのセッション、すべてのトランスクリプトが失われます。

これらは欠陥ではありません。それらは、キーボードを持つ一人のためのシェルを多重化するために構築されたツールの限界です。

crystlが引き継ぐ場所

crystlは、tmuxが想定されていなかったワークロード(多数のエージェントが同時に実行され、そのほとんどが画面外にあり、それぞれが時折あなたを必要とする)のために構築されたmacOSターミナルです。

違いは、上記のリストとほぼ1対1でマッピングされます。

  • 承認が埋もれるのではなく表面化する。 いずれかのエージェントが許可を求めて一時停止すると、crystlはそれをプロジェクトごとに色分けされたカードとしてフローティング表示し、セッションを切り替えることなく応答できます。スクレイピングもポーリングも不要です。floating cardsを参照してください。
  • エージェントの状態はファーストクラスのシグナルである。 agent activity panelは、すべてのセッションの状態(作業中、あなた待ち、アイドル、完了)を一度に表示します。これはステータスラインスクリプトが近似しようとしていたものです。
  • 出力は構造化されており、スクレイピングされない。 crystlは、各ターン、ツール呼び出し、および結果を型付けされたレコードとしてキャプチャするため、historyはすべてのセッションで検索可能であり、エージェントはANSIを解析せずに互いのトランスクリプトを読み取ることができます。
  • セッションはプロジェクトごとに整理され、永続化される。 プロジェクトはジェムであり、その中のセッションはシャードであり、ウィンドウを閉じたりMacを再起動したりしても履歴は永続化されます。multitaskingでさらに詳しく説明しています。
  • 単一リポジトリでの並列作業は分離される。 分離されたシャードは実際のgit worktreeによってバックアップされるため、同じリポジトリ上の2つのエージェントが衝突することはありません。parallel sessionsを参照してください。

推奨:tmuxは使い続けてください。リモートボックス、サーバー上の長時間実行プロセス、およびSSHリンクが脆弱なものには、依然として正しい答えです。それらのために使用してください。管理しているものが、それぞれ予測不可能な瞬間にあなたからの決定を必要とする6つのエージェントである場合、問題は多重化ではなくなります。

整理の側面についてはmanaging multiple AI coding sessionsで、エージェントをアイドルではなく作業状態に保つことについてはrunning AI coding agents for longerでさらに詳しく説明しています。