SUZ-LAB

ターミナル 1 枚に AI 開発コックピットを建てる — Claude / File / GitHub を tmux に畳んだ常駐環境

SSH でログインしたら、いつも同じ作業場にそのまま着地する——Claude Code を何本も並行で回しながら、ファイルも PR も同じ場所で行き来できる「常駐の開発コックピット」を tmux で組んだ記録。1 枚の tmux セッションに Claude(Agent View)/ File(lf ファイラー)/ GitHub(PR ブラウザ)の 3 ウィンドウを畳み、Shift+矢印で回す。再アタッチで最新化する自己修復、gh-dash をやめて gh + fzf の自前ブラウザに置き換えた判断、そして下段ステータスバーに要対応セッションを型別で光らせるクロスセッション通知まで、実際のスクリプトと公式仕様の裏取りつきで、一次情報として残します。

何をしたかったか — 繋いだ先に欲しいのは「常駐の作業場」

このリポジトリは Codespaces 上の Dev コンテナで回している。母艦には SSH でログインして 入る。繋ぐところまでは整っていた。けれど実際に手を動かし始めると、繋いだ先に欲しいものが 「素のシェル 1 枚」では足りないことがすぐ分かった。

行動原則の一つは「AI を使い倒す」だ。使い倒すとは、1 本の対話を丁寧に回すことではなく、 別々の関心事に Claude Code を何本も並行で走らせ、その合間にファイルを覗き、PR を捌く ことだった。そのたびに claudelfgh を手で立ち上げ直し、どこで何をしていたかを 思い出す——この摩擦が、並行で回す旨みを削っていた。

だから狙いを一つに定めた。SSH ログインしたら、いつも同じ「作業場」にそのまま着地する。 Claude を回すところ、ファイルを見るところ、PR を捌くところが、最初から 1 枚のターミナルに 畳まれていて、行き来するだけでいい。永続セッションなので、切れて戻っても同じ配置が待っている。 このコックピットを lab-view.sh として組んだ。以下、その一つひとつを、代替案や実際の つまずきと一緒に残す。

設計 — 1 セッション・3 ウィンドウ・矢印キーで回す

コックピットの骨格はシンプルだ。1 つの tmux セッションに 3 つのウィンドウを並べ、 ウィンドウ名をそのまま下段メニューの表記にする。

  • Claudeclaude agents(Agent View)を全画面で常駐。ログイン直後はここに着地する。
  • Filelf ファイラー。
  • GitHubgh + fzf の PR ブラウザ(gh-view.sh)。

この骨格を成り立たせるために、いくつか地味な判断を重ねた。

  • 専用ソケットで隔離する。 既定の tmux サーバを使うと、汎用セッション名や prefix 無しの 設定が他の用途とぶつかる。コックピットは専用ソケット(-L suzlab)に隔離し、その上で セッション名を lab にする。キー設定やステータスバーの変更が他の tmux へ漏れない。
  • ウィンドウ名 = メニュー表記。 下段メニューはウィンドウ名(#W)を index 順に並べる。 だから表示したい表記(Claude / File / GitHub)をそのままウィンドウ名に採り、Shift+矢印の 切替対象や自己修復の grep も同じ名前で回す。主役の Claude を最左(index 0)に作り、 ログイン直後のフォーカスをそこへ置く。
  • 切替は Shift + ←/→。 「左右に移動」という直感に合い、修飾キー付きなので素の矢印を 奪わない(矢印は lf / Claude / fzf が中で使う)。Claude → File → GitHub の循環にした。
  • セッションは無いときだけ作り、キー設定と GitHub ウィンドウは毎回当て直す。 セッション 本体を毎回作り直すと再接続で状態が飛ぶ。一方、キーバインドや GitHub ウィンドウの用意を 作成分岐の中だけに置くと、後から入れた fzf や設定変更が既存セッションに反映されない。 そこで本体は冪等に温存・付帯設定は毎回適用に分け、再アタッチのたびに最新化される ようにした(自己修復)。

Claude ウィンドウ — Agent View を常駐させる

主役のウィンドウは claude agents(Agent View)を全画面で開いたまま常駐させる。複数の Claude Code セッションの一覧・切替はここが担う。ログイン直後にここへ着地するので、「開いたら すぐ AI に投げられる」——同じ作業場に着地するという狙いが、そのまま作業場の入口になった。

Claude Code には Agent View / Agent Teams のような複数エージェントを束ねる native な導線が 育ちつつある(docs)。コックピットは、それを 1 枚のターミナルの中心に据えつつ、周りにファイルと PR の窓を足した恰好だ。

File ウィンドウ — lf に bat プレビューを噛ませる

ファイルを覗く窓は lf にした。既定で「左=ファイルリスト/右=プレビュー」の 2 列を出す (追加設定なし)。プレビューは bat でシンタックスハイライトと行番号つきにし、Enter で ファイル全体をページャ表示、← でファイラーに戻る、という往復にした。

ターミナルファイラーは ranger / yazi / nnn と選択肢が多い (2026 年の比較では、画像プレビューが素で効く・非同期でUIが固まらない yazi を新規に薦める 声もある)。それでも lf を採ったのは、 単一バイナリで依存が軽く、既定のまま 2 列プレビューが出て、Codespaces に置くのが楽 だったからだ。速さや多機能でなく、「この環境に薄く乗る」を優先した。

GitHub ウィンドウ — gh-dash をやめて gh + fzf にした

PR を捌く窓は、最初 gh-dash を使うつもりだった。GitHub CLI 拡張の TUI ダッシュボードで、repo 別セクション・Vim キー・YAML 設定を備えた成熟したツールだ (〜12k stars)。ところが Codespaces で詰まった。既定の GITHUB_TOKEN では branchProtectionRules を読む権限が無く、PR 一覧の取得がそこで固まる。

環境側のトークン権限に依存する不安定さを持ち込みたくなかったので、gh-dash はやめて、 gh pr listgh pr view / gh pr diffbranchProtectionRules を要求しない)を叩いて fzf で選ぶだけの薄い自前ブラウザ(gh-view.sh)に置き換えた。成熟した汎用ツールより、この 環境で確実に動く薄い自前を採る——ガードレールの監査で身につけた判断を、ここでも使った。

自前にしたぶん、細かい挙動も自分で決められる。gh-view の fzf は自分のイベント(起動・Ctrl-R・ 詳細/diff を閉じた後)でしか一覧を取り直さないので、tmux のウィンドウ切替で GitHub に戻っただけ では表示が古いまま残る。そこで GitHub ウィンドウへ遷移したら、その場で fzf に reload(Ctrl-R)を 送って最新化する tmux バインドを足した。条件を「window_name==GitHub かつ pane_current_command==fzf」に絞り、pager(less)や shell が前面のときには送らない。

下段の一機能 — 要対応セッションを型別で光らせる

3 ウィンドウを行き来していると、地味だが致命的な問題が出る。別ウィンドウにいるあいだ、 裏で止まった Claude セッションに気づけない。 Claude は権限確認や質問で頻繁に止まり、 こちらが入力を返すまで待つ。File や GitHub を見ているあいだ、それが放置される。

そこでコックピットの下段ステータスバーに、要対応セッション数を持続表示する一機能を足した。 先行事例(Marc Nuri は「5〜10 並行で手に 負えなくなる」と書き、人間が要るイベントだけ通知して alert fatigue を避けることを勧める)に 倣い、「要対応だけを、緊急度で型分けして出す」を原則にした。

意味いつ消えるべきか表示
入力待ちあなたの入力を待って Claude が止まっている対応する(プロンプト送信)まで持続赤・太字・点滅
完了回答・結果が出そろって準備 OK開いて読めばよい=時間で自動失効(既定 10 分)緑・定常

状態を捕まえる入口は Claude Code の hooks だ。 フックには stdin で JSON が渡り、その中の session_id がセッションを一意に識別する。 Notification→入力待ち / Stop→完了 / UserPromptSubmit→クリア / SessionEnd→残骸掃除、の 4 つを ~/.claude-notify/<session_id>.<type> というマーカーファイルの作成・削除に落とす。表示側は tmux の status-right#(claude-notify-status.sh) を呼び、.wait / .done を型別に数えて 色付き文字列を返す(0 件なら空文字=時計だけ)。status-interval 5 の再描画が、点滅を数秒周期の ちらつきとして見せる。

この一機能を作るなかで、公式仕様の裏取りが 2 つ効いた。

  • フックは何があっても exit 0 で抜ける。 公式ドキュメントによれば exit 2 は ブロッキングエラーで、しかもブロックできるイベントに StopUserPromptSubmit が 含まれるNotificationSessionEnd は exit code が無視される)。つまり通知の都合で フックが非ゼロで落ちると、セッション本体を妨害しかねない。通知が主役の邪魔をしては 本末転倒なので、全経路 exit 0 を徹底した。作法ではなく仕様上の必然だ。
  • 緑背景バッジ上の blink は背景ごと点滅する。 当初は完了も点滅させていたが、端末に よっては緑背景のステータスバー上で blink を使うと文字だけでなくセルの背景まで点滅する。 SGR の blink はセル単位の属性で、「背景据え置き・文字だけ点滅」を SGR だけで表す術が無い。 外部に裏の取れないこちらの手元の観察だが、だからこそ残す——完了は点滅をやめて定常にした。 結果、「点滅=要対応・定常=準備 OK」という素直な区別に落ち着いた。

なお同じ「どのセッションが待っているか」を、デスクトップ通知+ペインへジャンプで 解く先行事例や、claude agents --json を source of truthにして fzf で並べる例もある。コックピットが選んだのは割り込まない・依存を足さない・視界の端に 常駐する「下段バッジ」だ。前面を奪わず、作業中の集中を切らさずに「今 2 つ待っている」が分かる。

つまずき

  • 待機中に閉じたセッションの .wait が残り続ける。 入力待ちのまま閉じると、素朴な実装では マーカーが安全弁の掃除まで残り「入力待ちでないのに ● 入力待ち が出続ける」。SessionEnd で 入力待ちの残骸だけを消して塞いだ(完了表示は残す)。
  • session_id が取れないとき固定名に集約してはいけない。 unknown のような 1 名に寄せると 複数セッションが 1 件に衝突してカウントが狂う。誤通知を避けて no-op にした。
  • GitHub ウィンドウが古いまま。 他ウィンドウから戻ると fzf の一覧が更新されない → 遷移時に reload を送って最新化(前述)。
  • 新規作成時に GitHub に着地してしまう。 new-window は既定でフォーカスを奪うので、GitHub を -d で裏に用意し、ログイン直後のフォーカスを Claude に残した。

検証

  • ログイン → 着地。 SSH ログインで tmux セッション(lab)に自動アタッチし、Claude Agent View に着地。Shift+←/→ で Claude → File → GitHub を循環できること。
  • 自己修復。 再アタッチ時に GitHub ウィンドウ・キーバインド・fzf 最新化が効くこと。gh / fzf / 認証が揃わない環境では GitHub ウィンドウを作らず素直に落ちること。
  • 通知の往復。 Claude を止める → 下段に ● 入力待ち 1(赤点滅)→ プロンプト送信で消える。 応答完了 → ✓ 完了 1(緑定常)→ 10 分放置で自動失効。2 本止めれば ● 入力待ち 2。別ウィンドウに いても下段に出続けること。
  • 触れたのは devcontainer 側のスクリプトと .claude/settings.json なので、アプリのビルドには 影響しない。リポジトリ全体で pnpm typecheck / pnpm lint / pnpm format:check が通ること。

学び

  • 作業場は「配置」で決まる。 速いエディタや多機能なファイラーより、「開いたらいつも同じ 3 枚が 同じ場所にある」ことのほうが、並行で回す摩擦をずっと減らした。いつも同じ作業場に着地するという 狙いは、結局この地味な定位置化で達成された。
  • 成熟した汎用より、この環境で確実に動く薄い自前。 gh-dash を落として gh + fzf にしたのも、 lf を選んだのも、通知を割り込まない常時バッジにしたのも、根は同じだ。SSH で入る Codespaces という制約から逆算すると、選択は自ずと絞れる。
  • 端末の制約が、設計をより素直にすることがある。 完了も点滅させたかったが、緑背景の都合で 定常にした。結果、最初の型分けより分かりやすい表現に落ち着いた。制約は必ずしも妥協ではない。
  • 裏取りは「なんとなくの安全策」を「必然」に変える。 フックの exit 0 は、公式仕様を当たって 初めて「Stop / UserPromptSubmit を守るための必然」だと分かった。一次情報として書くと決めると、 自分の実装の理由まで確かめ直すことになる。
#tmux#Claude Code#SSH#lf#gh#DevContainer#マルチエージェント#hooks#AI

同じ柱のほかのログ

この柱の Roadmap を見る