# 「移行した」と「定着した」は違う — herdr を日々の母艦に馴染ませ、乗り換えた意味を取り戻すまで

> 前回「自前の通知機構を捨てて herdr へ全面移行した」と書いた。だが「移行した」は宣言で、「定着した」は毎日使って初めて分かる——その翌週から母艦に SSH で入り続けて埋めた穴の記録。最大の発見は、乗り換えの核心だったはずの「ペイン単位のエージェント状態検出」が、Claude タブで Agent View を全画面常駐させていたせいで実は届いていなかったこと（＝二重の多重化）。素の claude を 1 ペイン＝1 エージェントに割り直して初めて、移行の意味が出た。あわせて、タブ起動をプラグインへ畳み、起動系をヒアドキュメントから「追跡ファイル＋symlink」へ寄せ、その代償（ツールの自動書き戻しで dirty になる税金）を意図的な固定で払い、そして SSH × マルチプレクサ × 全画面 TUI の三重の間でコピペとスクロールが何度も壊れ、copy_mode を足して revert し、最後は「マルチプレクサではなく TUI 自身のスクロールに委ねる」に着地するまで——想定と違う決着まで正直に残します。

- 出典: SUZ-LAB（実験ログ / 一次情報）
- URL: https://suz-lab.co.jp/logs/settling-into-herdr
- 公開: 2026-07-22
- 更新: 2026-07-22
- 著者: Hiro（主任研究員・https://suz-lab.co.jp/researchers/hiro・X: https://x.com/suz_lab_hiro）
- 柱: テクノロジー
- タグ: #herdr #Claude Code #SSH #DevContainer #マルチエージェント #プラグイン #lf #端末 #AI
- カバー画像: https://suz-lab.co.jp/assets/logs/settling-into-herdr.png?v=1e1ed789

---


## 「移行した」は、まだ半分だった

つい先日、[自前の通知機構を捨てて herdr へ全面移行した回](/logs/migrating-to-herdr-agent-multiplexer)を書いた。tmux で組んだ[開発コックピット](/logs/ai-cockpit-cross-session-notifications)の本丸だったクロスセッション通知（フック4種＋シェル2本）を丸ごと捨て、AI エージェント特化マルチプレクサ [herdr](https://github.com/ogulcancelik/herdr) の**ネイティブなペイン状態検出**（`idle` / `working` / `done` / `blocked`）へ載せ替えた——という記録だ。あの回は「検証で通したところ」と「まだ確かめきれていない穴（`blocked` の実発火・iPad での操作感）」を分けて、正直に書いたつもりだった。

その続きがこれだ。**移行を宣言してから、翌週いっぱい母艦に SSH で入り続けた**。そうして分かったのは、「移行した」と「定着した」は別物だということだ。前回の PR で構造は載せ替わっていたが、**日々使って初めて見える穴**がいくつもあった。しかも最大の穴は、移行の**核心**そのものにあった。

この記録は、その定着の過程で埋めた穴の一覧だ。設計判断だけでなく、**足して revert した徒労**や、**想定と違う形で決着した穴**まで、そのまま残す。

## 乗り換えた意味が、実は届いていなかった — 1 ペイン＝1 エージェントへ

前回いちばん胸を張ったのは「herdr は**ペイン単位で**エージェントの状態を検出する」ところだった。ところが実運用で Claude タブを開いても、サイドバーに個々のセッションの状態が出ない。調べて、青ざめた。

Claude タブは、当時まだ `claude agents`（Agent View）を**全画面で常駐**させていた。すると複数の Claude セッションが**単一ペインの中に入れ子**になる。herdr から見えるのは**外側の 1 ペインだけ**で、中の個々セッションの `idle` / `working` を出しようがない。前回わざわざ乗り換えた「ペイン単位の状態検出」が、この構成では**まったく効いていなかった**——tmux の中で tmux を動かすような、**二重の多重化**をしていたわけだ。

<Details summary="なぜ状態が「取り違う」のか — 実機で見えた壊れ方">

herdr の claude 連携フックは `SessionStart` で「このペイン（`HERDR_PANE_ID`）で動いている session-id はこれ」というバインドを張る。ところが 1 ペインに複数セッションが入れ子だと、**後から起動したセッションが同じペインの前のバインドを上書き**する。実機では、フォーカス中のペイン `p1` の状態が、まったく別のバックグラウンド・ジョブ（`e5a4fc12…`）の状態を指しているのを確認した。加えて表示ステータスは Agent View のダッシュボード端末を**スクレイプ**した二次情報で、これも不正確だった。要するに、状態検出の入力（pane→session バインド）が構造的に壊れていた。

</Details>

対処はシンプルで、しかし破壊的だった（PR #125）。**Claude タブを素の `claude` 1 セッションに戻し、1 ペイン＝1 エージェント**にした。セッションを増やすときは Agent View ではなく herdr ネイティブのファンアウトを使う:

```bash
herdr agent start Claude1 --split right -- claude   # 右に分割して新しい claude
herdr agent start Claude2 --tab <tab_id> -- claude  # 指定タブ内に起動
```

こうすると各 `claude` が**個別ペイン＝個別エージェント**としてサイドバーに並び、`agent_panel_sort = "priority"` の「要対応が上」ソートが初めて機能する。**移行の核心的な価値は、載せ替えた瞬間ではなく、この割り直しで初めて出た**。「乗り換えた」と書いた翌週に、「まだ乗り換えられていなかった」と気づく——定着とは、そういう作業だった。

## タブ起動を、herdr プラグインへ畳む

1 ペイン＝1 claude に切り替えると、タブの作り方・ファンアウトの持ち方も見直しになった。ここで herdr 0.7.x の**プラグイン機構**へ寄せた（PR #127、その後 #128 / #129 で命名を整理）。

- **同梱プラグイン `suzlab.view`**（`.devcontainer/herdr-plugin/`）に、3 タブの起動コマンドを `[[panes]]`（`claude` / `lf` / `bash -l`）として、ファンアウトを `[[actions]] fanout`（`fanout.sh`。空いている最小の `Claude<N>` を採番して新タブで起動）として集約した。ランチャー（`herdr.sh`）は起動ごとにこれを冪等 `herdr plugin link` し、レイアウト構築を `herdr plugin pane open` に置き換えた。旧 `~/.local/bin/claude-fanout` の生成は削除。
- **初期タブ順を左→右で `Shell | File | Claude0`** にした。herdr には**タブ並べ替えが無く、表示順＝作成順**なので、root タブを `Shell` にして、その右へ `File` → `Claude0` を作成順に足す。着地は最右の Claude。ただしこの順が保証されるのは**ワークスペース新規作成（初回ログイン）のときだけ**で、既存ワークスペースや自己修復での復活は末尾に付く——ここは仕様として `AGENTS.md` に明記した。
- **主 Claude タブを `Claude0` に改名**（PR #128）。fanout が採番する `Claude1` / `Claude2`… の**先頭＝0 番目**として並びが揃う。あわせてランチャー名・ワークスペースラベル・プラグイン ID を **`suzlab` 系に統一**（PR #129。`lab-view.sh` → `herdr.sh`、`plugin_id = suzlab.view`、ワークスペースラベルは Codespaces のリポジトリ名へ縮退）。
- **File タブは `lf` を継続**。herdr マーケットプレイスのプラグインを 198 件調べたが、`lf` 相当のファイラーは無く、候補（別ツール＋ビルド依存＋未審査）は採用を見送った——[前回の gh-dash 判断](/logs/migrating-to-herdr-agent-multiplexer)と同じ「この環境で確実に動くか」を軸にした見送りだ。

「守る側／土台のコード」は機械の目が効く、という[lefthook 回](/logs/migrating-git-hooks-to-lefthook)の学びはここでも当たり、Copilot レビューを 4 ラウンド回した（focus の誤着地、`jq` パースの明示ガード、`herdr agent start` 失敗時に**空タブへ着地しない**縮退、`AGENTS.md` のタブ順注記）。ファンアウトが「唯一のペインを失った空タブ」に着地しうる不整合は、実際に指摘で拾って直した。

## 起動系を「ヒアドキュメント」から「追跡ファイル＋symlink」へ

前回は herdr の設定も連携フックも「起動時に**冪等生成**」していた。実体はシェルスクリプト中の**ヒアドキュメント**で、毎起動で `config.toml` や `~/.bashrc` のブロックを吐き直す方式だ。これを、**リポジトリ同梱の追跡ファイル**へ実体を出す方式に畳んだ（PR #130 / #131）。lf 設定でやっていた「repo ファイル＋symlink」と同じ型だ。

- **SSH ログインフックを外部ファイル化**（PR #130）。ログイン起動ロジックを `~/.bashrc` のヒアドキュメントから追跡ファイル `ssh-login-hook.sh` へ切り出し、rc に貼るのは `source` の 2 行だけにした。rc のマーカーもタブ構成の**世代から脱結合**して固定文字列 `login hook` にしたので、フックの中身を変えても rc は不変になり、世代バンプや孤立ブロックによる握り潰しが起きなくなった。旧 `(SSH → …)` 系のマーカーブロックは、以前の「警告のみ」から**自動除去**へ切り替えた。
- **`config.toml` を追跡ファイル化**（PR #131）。ヒアドキュメント生成をやめ、実体を `.devcontainer/herdr/config.toml` に置き、`~/.config/herdr/config.toml` をそれへの symlink に冪等収束させる（別リンク先・壊れリンク・実ファイルなら張り直し、目的の symlink 済みなら何もしない）。追跡ファイルが欠けていれば link せず素の herdr へ縮退する。

利点は、設定が**TOML／シェル単体で検証・編集できる**ことだ。ヒアドキュメントは「起動して初めて確定する設定」で、単体では読みにくく壊れやすい。だが repo の実体に寄せると、代わりに税金がつく——それが次の節だ。

<Details summary="rc に launcher パスを二重管理しない">

`~/.bashrc` が持つのは「このフックへの `source` 行」だけで、launcher（`herdr.sh`）の絶対パスは rc に焼き込まない。フックは自分のファイル位置（`BASH_SOURCE`）から同じ `.devcontainer` の `herdr.sh` を解決するので、フックと launcher は「同じディレクトリに並ぶ 1 組」として常に整合する。ただし Copilot 指摘で 1 点訂正した——**リポジトリ自体を別パスへ移動した場合**は、rc の `source` 行が移動前のパスを指したままなので、post-create の再実行（rebuild）まで追随しない。「置き場所が変わっても追随する」は過剰主張だった。

</Details>

## repo に設定を持つ税金 — ツールの自動書き戻し

`config.toml` を追跡ファイル本体にした（`~/.config` 側は symlink）ことで、思わぬ副作用が出た。**herdr はオンボーディング完了／スキップ時に `onboarding = false` を config へ書き戻す**。symlink 経由なので、この書き戻しが**追跡ファイル本体を書き換え**、毎回ワークツリーが dirty になる。実際、あるスクロール実験の PR（後述の #135）に、この書き戻しが**無関係な差分として混入**した。

対処は「消す」ではなく「**固定する**」だった（PR #136）。devcontainer は使い捨て・再作成が前提なので、`onboarding = false` を repo として**明示的にコミット**して「セットアップ済み」を固定し、再作成のたびにウェルカム画面が出ないようにしつつ、自動書き戻しによる dirty を封じた。**設定を単一の出所（repo の追跡ファイル）に寄せると、ツールが勝手に書き戻す値まで自分の管理下に置くことになる**——その代償を、意図的な固定で払った格好だ。

## コピペとスクロール — 三重の間で何度も壊れた

いちばん手こずったのは、**コピペとスクロール**という端末の地味な当たり前だった。母艦は Mac の Terminal.app や [iPad の Blink](/logs/ipad-blink-codespaces-tmux) から SSH で入る。SSH × マルチプレクサ × 全画面 TUI（Claude Code）の**三重の間**で、これが何度も壊れた。

**まずコピペ。** herdr 既定（`mouse_capture = true`）だと herdr がマウスドラッグを横取りし、mac ターミナルからのネイティブ範囲選択→Cmd+C ができない。`[ui]` に `mouse_capture = false` を足して端末側の選択に任せた（PR #132）。ところが**Claude タブだけ**まだ選択が奪われる。原因は、ペイン内の Claude Code が**自前でマウストラッキングを ON** にしていたことだった。`CLAUDE_CODE_DISABLE_MOUSE=1` で Claude Code のマウス捕捉だけを切って解決した（PR #134）。トレードオフとして Claude 内のクリック操作・ホイールスクロールは無効になり、スクロールは別手段が要る——という次の問題に直結した。

**そのスクロールで、足して revert する徒労をやった。** ホイールが無効になったので会話履歴を遡れない。そこで会話履歴をレンダリングしたまま辿れる `copy_mode` を `prefix+u` に割り当てた（PR #135。生 ANSI を出す `edit_scrollback` は文字化けするため避けた）。ところがこれを**翌 PR で revert した**（PR #137）。

理由はこうだ（PR #138 で明文化）。`claude` は**全画面（alternate screen）TUI** で、会話履歴を**端末／herdr のスクロールバックに書き出さない**。実際 `herdr pane read <pane> --lines 200` はビューポート分（34 行）しか返さず `max_offset_from_bottom = 0` だった。**スクロールする対象がそもそも herdr 側に無い**のだから、`copy_mode` も `edit_scrollback` も Claude ペインでは効きようがない。`copy_mode` を足したこと自体が**無意味**だった。

正解は「**マルチプレクサでスクロールしようとしない**」だった。会話スクロールは **Claude Code 自身のキー**に委ねる——これは全画面 TUI アプリの責務で、外側のマルチプレクサの仕事ではない。**機能がどのレイヤーの責務かを見誤ると、丸ごと徒労になる**、という学びだ。

最後に、そのキー割り当てを**Mac と iPad で揃えた**（PR #139）。当初は Mac 前提で `Fn+↑↓←→`（PgUp 等）を案内していたが、**iPad Magic Keyboard には Fn が無い**ので押せない。`Ctrl+英字`だけで完結する `Ctrl+O` のトランスクリプトモード（`k`＝上 / `j`＝下 / `g`・`G`・`/`検索・`q` 退出）を Mac / iPad 共通の推奨に前面化し、`Fn+矢印` は Fn 付き Mac 限定の補助へ降格した。前回「iPad での操作感が未確認」と残した穴は、`Shift+矢印`ではなく**スクロールという別の入口**から、iPad 前提が効く形で決着した。

## 検証 — 通ったところと、まだ残る穴

前回の作法を継いで、通したところと未確認を分けて残す。

- **通った。** 1 ペイン＝1 エージェントでサイドバーに個々の Claude の状態が出ること（`priority` ソートで要対応が上に来ること）。fanout が `Claude<N>` を採番して新タブに開くこと。初回ログインで `Shell | File | Claude0` の左→右順に並ぶこと。`config.toml` の symlink 収束（新規／誤リンク／実ファイル／冪等／source 欠落縮退の 5 ケース）と、rc の `source` 行移行。mac Terminal.app からのネイティブ選択→Cmd+C コピペ。`Ctrl+O` トランスクリプトでの会話スクロール（Mac で確認）。触れたのは devcontainer 側のスクリプトと設定だけなので、`pnpm typecheck` / `pnpm lint` / `pnpm format:check` が通ること。
- **まだ残る穴。** `blocked` の実発火は、本リポジトリが `bypassPermissions` 運用のため許可プロンプト由来では出にくく、**前回から引き続き実運用の遷移として見届けられていない**。`Ctrl+O` は設計上 iPad Magic Keyboard で完結するはずだが、**実機での配信・操作感の体感確認は今後**。新タブ順（`Shell | File | Claude0`）が保証されるのは**新規ワークスペースの初回ログインのみ**で、既存ワークスペースは並べ替わらない。

## 学び

- **「移行した」は宣言、「定着した」は毎日使って初めて分かる。** 乗り換えの核心（ペイン単位の状態検出）が、Agent View の全画面常駐という**二重の多重化**で実は届いていなかった。それを炙り出したのは翌週の日常運用だった。構造を載せ替えただけで「終わった」と思わないこと。
- **設定を単一の出所（repo の追跡ファイル）に寄せると、税金がつく。** ヒアドキュメント生成をやめて TOML／シェルの実体に寄せると単体で検証・編集できる一方、ツールが勝手に書き戻す値（`onboarding`）まで自分の管理下に入り、dirty を生む。代償は「消す」でなく「意図的に固定する」で払う。
- **機能がどのレイヤーの責務かを見誤ると、丸ごと徒労になる。** スクロールを「マルチプレクサの機能」で解こうとして `copy_mode` を足し、全画面 TUI がスクロールバックを持たないと理解して revert した。責務の切り分けを間違えた分だけ、手戻りする。
- **iPad 前提は「補助キーが無い」制約として効く。** `Fn` なしで完結する操作（`Ctrl+英字`）に寄せると、Mac / iPad 共通で楽になる。母艦をどこから触るかが、キー設計の制約になる。
- **足して revert した記録も、一次情報として残す価値がある。** 定着は、うまくいった設計判断の裏で徒労と手戻りを払う過程でもある。前回が「検証」を丁寧に書いたぶん、続編で徒労を隠さないことに意味がある。

