Skip to content

feat: tmux のセッションを名指しで attach・調査・終了できるようにする(名指しのコマンドと tmux 組み込みの TUI) #234

Description

@takemi-ohama

何を見つけたか

tmux-first / tmux-clean(containers/base/ から /usr/local/bin へ焼き込み。ホストでは ~/.local/bin に同じ中身)は、同じベース名のセッション群をまとめて片付ける作りで、1 つのセッションを名指しする操作ができない。素の tmux コマンドには落とし穴がある。

日常でよく使うのに、今の道具では手間がかかる操作は次の 3 つである。

やりたいこと 今の道具でできない理由
狙ったセッションへ強制 attach する(既に attach している端末は外してよい) tmux-first は常に「ベース名で一番番号の小さいもの」を選ぶ。devbase-3 を名指しできない。操作中の端末を外すには -f が要る
他の端末が attach しているセッションで、何が動いているかを調べる 道具が無い。pane_current_command はフォアグラウンドのコマンドしか出さない(例: devbase list の TUI が uv とだけ見える)
狙ったセッションを、attach の有無に関係なく落とす tmux-clean は keeper(残す 1 つ)以外を消す作りで、名指しできない。アタッチ中・実行中は -f が要り、-f では keeper 以外がすべて消える

素の tmux での手順(tmux 3.7b で確認)

セッション名の前の = は完全一致の指定で、devbase-1 が devbase-10 に前方一致で当たるのを防ぐ。

1. 強制 attach

# tmux の外から: -d で他の端末を外してから attach する
tmux attach -d -t =devbase-3

# tmux の中から: 先に相手の端末を外し、それから自分が移る
tmux detach-client -s =devbase-3
tmux switch-client -t =devbase-3
  • tmux の中では順序が逆だと自分も外れる(先に移ってから detach-client -s すると、自分も対象のセッションの端末になっている)
  • detach-client -a は「自分以外のすべての端末」で、関係のない別のセッションの端末まで外す

2. 中身を調べる(attach しない)

S=devbase-2
tmux list-clients -t "=$S" -F '#{client_tty}  最終操作 #{t:client_activity}'
tmux list-panes -s -t "=$S" -F '#{window_index}.#{pane_index}  #{pane_current_command}  pid=#{pane_pid}  #{pane_current_path}'
tmux capture-pane -p -t "=$S" -S -40      # 画面の直近 40 行(-e で色付き)
pgrep -P <pane_pid> -lf                   # シェルの子プロセス(& で起動したものも出る)
tmux attach -r -t "=$S"                   # 読み取り専用で覗く(-r = read-only,ignore-size。相手の画面サイズも変えない)

3. 名指しで落とす

tmux kill-session -t =devbase-3           # attach 中でも落ちる(相手は [exited])
tmux kill-session -a                      # 今いるセッション以外すべて

中のプロセスには SIGHUP が届く。nohup で起動したものは残る。

直し方

2 層に分ける。土台に名指しのコマンドを置き、TUI は tmux 自身の UI 機能で薄く載せる。

層 中身 依存
土台 名指しの 3 操作をシェルのコマンドにする(例: tmux-go <名前> / tmux-peek <名前> / tmux-kill <名前>...、または tmux-first -t <名前> の追加) sh のみ
TUI tmux の choose-tree / display-menu / display-popup で「一覧 → 操作」のメニューを組み、操作は土台のコマンドを呼ぶ 0(tmux のみ)

土台だけでも 3 つの操作は完結する。TUI が使えない場面(tmux の外・非対話)でも土台が残る。

土台: 強制 attach の注意

tmux-first が「実行元のクライアントを特定できない」ときに止まるのと同じ配慮が要る。自分の端末を #{client_name} で除外してから相手を外す。

me=$(tmux display -p '#{client_name}')
tmux list-clients -t "=$1" -F '#{client_name}' | grep -vxF "$me" | xargs -n1 tmux detach-client -t
tmux switch-client -t "=$1"

TUI: tmux の組み込み機能で組む

3 つの操作のうち 2 つは choose-tree が既に持っている。

やりたいこと tmux の機能 足す量
一覧から選ぶ・中身を見る choose-tree -s が全セッションの一覧と、選んでいるセッションの画面のライブなプレビューを出す(v で表示の切り替え、f で絞り込み) 0
落とす choose-tree の中で x(確認あり)。t で複数に印を付けて X でまとめて 0
強制 attach choose-tree の template から土台の tmux-go を呼ぶ キーの割り当て
プロセスの木まで見る display-popup -E で浮いた窓に tmux-peek の結果を出す キーの割り当て

形のイメージ(スケッチ。未実行):

# prefix S: セッションを選んで操作のメニューを出す(devbase の TUI と同じ「一覧 → 操作」の形)
bind-key S choose-tree -Zs -O name "run-shell 'tmux-session-menu \"%%\"'"
# tmux-session-menu: 選んだセッションに対する操作を tmux のメニューで出す
s=${1%:}
tmux display-menu -T "#[align=centre]$s" \
  "移る(他の端末を外す)" a "run-shell 'tmux-go $s'" \
  "中身を見る"            p "display-popup -E -w 90% -h 80% 'tmux-peek $s; read -r _'" \
  "落とす"                k "confirm-before -p '$s を落としますか? (y/n)' 'kill-session -t =$s'"

%% が選んだ項目にどう展開されるか(末尾の : の有無)と、メニューの中の引用符の入れ子は、実装で確かめる。

tmux の UI を採る理由:

  • 追加の依存が 0。 置き場所も今と同じで、/etc/tmux.conf(base へ焼き込み済み)と /usr/local/bin(tmux-first と同じ)に収まる
  • コンテナとホストで同じ UI になる。 ホストの tmux は 3.7b で、~/.tmux.conf に同じ割り当てを書けば動く
  • 使う機能はコンテナの tmux 3.6 ですべて使える(display-menu は 3.0 以降、display-popup は 3.2 以降)

弱点: tmux の外からは呼べない。VS Code の端末が常に tmux の中で開く運用なら実害は小さい。

検討した他の作り方

base イメージに入っているかを、イメージの中で確かめた(2026-09-22、devbase-base:latest arm64)。

候補 base に入っているか 判断
tmux 3.6 の組み込み機能 あり 採る(上)
Python 3.14 の標準ライブラリ curses あり(import curses が通る) tmux の外からも使いたくなったときの次の手。追加 0 だが、描画・キー操作・端末サイズの変化・日本語の表示幅を自前で扱い、200〜300 行ほどになる見込み
devbase の TUI と同じ questionary(その下は prompt_toolkit) なし(import prompt_toolkit が ModuleNotFoundError。devbase の CLI もコンテナに無い) 採らない。下の「questionary の重さ」
bash の select あり 番号で選ぶだけでプレビューが無い。curses も使えない環境の最後の受け皿
Node 24 あり。ただし TUI のライブラリは無い(グローバルは gemini-cli・codex・playwright など) 採らない。ink などを npm で足す必要がある
whiptail / dialog / fzf / gum / Perl の Curses なし 採らない

questionary の重さ

ライブラリ自体は重くない。 採らないのは、コンテナに無く、プレビュー窓も無いためである。

項目 実測(2026-09-22)
devbase の依存の木 questionary 2.1.1 → prompt-toolkit 3.0.52 → wcwidth 0.8.1
import の時間(ホスト、uv run 経由、温まった状態) 0.10〜0.17 秒
base イメージの apt python3-prompt-toolkit 3.0.52-2 / Installed-Size 1466 KB / 依存 python3-wcwidth
base イメージの apt python3-questionary 2.1.1-2 / Installed-Size 1269 KB。Depends に libjs-jquery / libjs-sphinxdoc / sphinx-rtd-theme-common(文書の生成物)が付く
  • コンテナへ入れるなら apt で入るが、python3-questionary は文書の生成物を Depends で連れてくる
  • questionary の select にはプレビュー窓が無い。プレビューを出すなら prompt_toolkit を直接使って全画面のアプリにすることになる
  • ホストでは devbase の venv に既に入っているため、devbase の TUI にカテゴリとして足すなら追加 0。ただしコンテナとホストで UI が分かれる

修正レイヤー

現象レイヤー: tmux のセッションを名指しで扱う操作が、素の tmux の組み合わせでしかできない。

修正レイヤー: containers/base/tmux-first / tmux-clean(と新設する名指しのコマンド)、containers/base/tmux.conf(キーの割り当て)。ホストの ~/.local/bin の同じ中身は手で置かれたもので、配布の経路が無い。

決めること

  • 名指しの操作を tmux-first / tmux-clean の引数として足すか、別のコマンド(tmux-go / tmux-peek / tmux-kill など)にするか
  • TUI のキー割り当て(prefix S は既定で割り当てが無いか、既存の割り当てと衝突しないか)
  • ホストの ~/.local/bin と ~/.tmux.conf へ配る経路を作るか
  • tmux の外からも使う要求が出たら curses で作るか(今は作らない)

由来

v3.7.0 の作業中に、ホストの tmux で devbase-2〜-4 を閉じる操作をしたとき。

進行

モード: standard / 作業ツリー: /Users/takemi_ohama/devbase/.worktrees/feature/plan69-tmux-named-session / 計画: issues/PLAN69_tmux-named-session.md

  • 要求と受け入れ条件 — 2026-09-24 10:29
  • 作業場所の用意 — 2026-09-24 10:30
  • 設計 — 2026-09-24 10:37
  • 素材の収集と出典の確定
  • ドキュメント再構成 — 2026-09-24 10:40
  • ドキュメントレビュー — 2026-09-24 10:41
  • 計画 — 2026-09-24 12:22
  • 実装 — 2026-09-24 12:23
  • 構造改善 — 2026-09-24 12:36
  • 実装レビュー — 2026-09-24 13:20
  • 完了判定 — 2026-09-24 14:14
  • Pull Request — 2026-09-24 14:18
  • 確定仕様化 — 2026-09-24 18:52
  • 後片付け — 2026-09-24 12:22
  • 配布 — 2026-09-24 19:18
  • 体裁レビュー
  • リリース後テスト — 2026-09-24 19:26
  • 振り返り — 2026-09-24 19:29

振り返り: #262 (comment)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions