# 作り込んだ翌週に捨てる — 自前の通知機構を AIエージェント特化マルチプレクサ herdr へ全面移行した記録

> 前回 tmux で建てた「AI 開発コックピット」の本丸は、別セッションで止まった Claude に気づくための自前の要対応通知機構だった（claude-notify.sh + status.sh + hooks 4種）。その3日後、この自前資産を丸ごと捨て、AIエージェント特化マルチプレクサ herdr のネイティブなペイン状態検出（idle/working/done/blocked）へ載せ替えた。段階移行（PoC → labh オプトイン併存 → 既定切替 → tmux 完全撤去）で可逆性を保ちながら進め、GitHub タブと専用依存（gh-view/fzf）まで削り 3タブ Claude/File/Shell に絞った。「この環境で確実に動く薄い自前」から「成熟した専用ツールに素直に乗る」へ——前作の学びを自分で更新した判断と、pre-1.0 依存に乗るための備え、そして正直に残る未検証の穴まで、実際の PR と一次情報で残します。

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

---


## なぜ、翌週に捨てたか

つい先日、[ターミナル 1 枚に AI 開発コックピットを建てた回](/logs/ai-cockpit-cross-session-notifications)を書いた。SSH でログインしたら同じ作業場に着地し、Claude を何本も並行で回しながらファイルも PR も 1 枚の tmux に畳む——という常駐環境だ。あの回の**本丸**は見た目の 3 ウィンドウではなく、下段に仕込んだ**クロスセッション通知**だった。別ウィンドウにいるあいだ、裏で止まった Claude に気づけない。だから Claude Code の hooks（Notification / Stop / UserPromptSubmit / SessionEnd）を `~/.claude-notify/<session_id>.<type>` というマーカーに落とし、`status-right` で型別に数えて赤点滅／緑定常で光らせた。フックは主役の対話を妨げないよう全経路 `exit 0`、SGR の `blink` が緑背景ごと点滅する端末癖まで回避した——**そこそこ作り込んだ**。

その3日後に、これを丸ごと捨てた。

きっかけは、AIエージェント特化のターミナルマルチプレクサ [herdr](https://github.com/herdr) を実機で触ったことだ。herdr は**ペイン単位でエージェントの状態（`idle` / `working` / `done` / `blocked`）をネイティブに検出**する。前回わざわざフック4種とシェル2本で組んだ「誰が入力待ちか」が、ツールの標準機能として最初から乗っている。自前で持ち続ける理由が、その瞬間に消えた。

前作で私は「成熟した汎用ツールより、この環境で確実に動く薄い自前」と書いた（gh-dash をやめて gh + fzf にした判断だ）。今回はその逆を選んでいる。矛盾ではない。**判断の軸は「環境で確実に動くか」で一貫していて、ツールの成熟度が上がった分だけ答えが反転しただけ**だ。作った翌週でも、捨てる理由が立てば捨てる。この記録は、その載せ替えの一部始終だ。

## 段階移行 — 可逆性を保ったまま既定を切り替える

「作った翌週に捨てる」ことのリスクは、勢いで壊すことだ。だから移行は**段階に切って、各段で戻せる**ように進めた。

- **Phase 0（PoC・検証）** … herdr `v0.7.3` を devcontainer に実機導入し、`idle → working → done` の**実遷移**を自分の目で確認した。移行の本丸＝自前通知機構を herdr のネイティブ状態検出で置換できることを、ここで裏取りしてから先へ進めた（検証後はいったん完全撤去）。
- **Phase 1-2（オプトイン併存）** … 当初は `labh` という別ランチャーで herdr をオプトイン追加し、既存の `lab`（tmux）と**併存**させた。herdr の claude 連携フックは herdr ペイン内（`HERDR_ENV=1`）でしか動かず、tmux 配下では即 `exit 0` で抜けるので、両者は安全に同居できる。「気に入らなければ `lab` に戻ればいい」状態を挟んだ。
- **Phase 3（既定切替＋撤去）** … 手応えが出たので既定を切り替え、`labh` を廃止して `lab` 一本に戻した（`lab-view-herdr.sh` → `lab-view.sh` にリネーム、旧 tmux 版は削除）。ここまでを **PR #115** に統合した。

トリガー式に後回しを管理する——[lefthook 移行の回](/logs/migrating-git-hooks-to-lefthook)で身につけた「TODO は期日でなく着手条件で寝かせる」を、ここでも使った。Phase 0 の検証結果が Phase 3 への go サインそのものになった。

## 引き算の中身 — 移行の核は「削除」だった

この移行は機能追加の物語ではない。**消したものの一覧**が、そのまま設計だ。

- **自前通知機構を撤去。** `claude-notify.sh` / `claude-notify-status.sh` を削除し、`.claude/settings.json` の hooks 4種（Notification / Stop / UserPromptSubmit / SessionEnd）を外した。要対応状態は herdr のサイドバー（`agent_panel_sort=priority`）が受け持つ。前作の本丸を、そっくりツールへ返した格好だ。
- **tmux 自体を撤去（PR #116）。** 当初は手動フォールバックとして apt に tmux を残していたが、続けて削除した。herdr 未導入時の縮退は「素のログインシェルに着地」へ。二重起動ガードは `$TMUX` → `$HERDR_ENV` に、要求ツールは `tmux/lf` → `herdr/claude` に置き換えた。これで tmux は devcontainer から完全に消えた。
- **GitHub タブと専用依存を撤去（PR #117）。** 前作で「gh-dash をやめて自前で組んだ」と胸を張った gh + fzf の PR ブラウザ（`gh-view.sh` / `gh-view.lesskey` / `install_fzf`）を、まるごと落とした。タブは Claude / File / **GitHub** / Shell の 4 枚から、**Claude / File / Shell の 3 枚**へ。`gh` CLI 自体は残置しているので、PR は Claude か Shell から叩けばよく、専用タブと fzf 依存を抱える割に合わなかった。作ったばかりの自前を捨てるのは二度目だが、判断の軸は同じだ。

herdr の設定（`config.toml`）と連携フックは、起動時に**冪等生成**する。`Shift + ←/→` のタブ切替は前作からそのまま持ち込んだ。

## 自己修復するタブ — 消えても再ログインで戻る

前作の tmux コックピットには「再アタッチのたびに最新化する自己修復」を仕込んでいた。同じ思想を herdr でも実装したが、herdr はワークスペース状態を**ディスクに永続する**ぶん、壊れ方が具体的で、対応も具体的になった（PR #119 / #120、仕様は #121 で `AGENTS.md` に明文化）。

想定した壊れ方は2つ。

- **状態A：タブそのものが消えた。** Claude タブ（Agent View）が無くなっている。→ 再ログインで作り直す。
- **状態B：タブは在るが、中身が素シェルに戻った。** ラベルは Claude なのに Agent View でなく素のシェルが居座っている。→ これも検出して中身を張り直す。

再ログインでこの A / B を**一括で自己修復**し、Claude タブが消えても復活するようにした。「配置は永続する」ことは再接続で状態が飛ばない利点だが、裏返せば**壊れた配置も永続してしまう**。だから「毎回作り直す」ではなく「在るべき姿と照合して、欠けているものだけ張り直す」に寄せる必要があった。

## 稼働中インスタンスへ設定を波及させる

「設定は起動時に冪等生成」だけでは足りない場面が、移行後に出た。**すでに立ち上がっているインスタンスに設定変更が届かない**のだ。lf の設定を書き換えても、稼働中の lf は古いまま動き続ける。そこで `lf -remote source` で稼働中インスタンスへ設定を送り込み、**起動時生成と稼働中反映の両方**を押さえた（PR #122）。あわせて lf の配色の precedence について、ドキュメントの誤記（colors ファイルが最優先で shadowing は無い、が正）を訂正した（PR #123）。地味だが、「設定した＝反映された」ではない、を踏んだ記録として残す。

## pre-1.0 のツールに乗るということ

herdr は魅力的だが、**pre-1.0・単独メンテ・AGPL** だ。母艦の日常運用をここに預ける以上、いくつか腹をくくった。

- **バージョンを固定する。** pre-1.0 は設定 API を破壊的に変えうる。`install_herdr`（post-create）で `HERDR_VERSION=0.7.3` にピンし、`~/.local/bin` へ冪等導入する。「勝手に上がって壊れる」を封じるのは、pre-1.0 に乗る最低条件だ。
- **状態がディスクに永続することを理解して使う。** herdr はワークスペースのレイアウトをディスクに残す。`server stop` してもレイアウトは消えない。便利さと表裏の性質で、前述の自己修復もこの前提の上に成り立っている。

<Details summary="なぜ「薄い自前」を捨てて pre-1.0 に乗れたのか">

前作の gh-dash 判断（成熟した汎用ツールを、環境のトークン権限問題で落とした）と、今回の herdr 判断（pre-1.0 の専用ツールに、自前資産ごと乗り換えた）は逆に見える。だが軸は同じ「**この環境で確実に動くか**」だ。gh-dash は Codespaces の `GITHUB_TOKEN` 権限に依存して固まった——環境で確実に動かなかった。herdr は実機の Phase 0 で `idle → working → done` を確認できた——**この環境で確実に動いた**。成熟度そのものではなく、目の前の環境での実動が判断基準だった。だから pre-1.0 でも、検証を通れば乗る。

</Details>

## 検証 — 通ったところと、正直に残る穴

手を動かして確かめたのは次の通り。

- **ログイン → 着地。** SSH ログインで herdr の `lab` に自動アタッチし、Claude タブに着地。`Shift + ←/→` で Claude → File → Shell を循環できること。
- **自己修復。** タブが消えた状態A・素シェルに戻った状態B のどちらも、再ログインで復活すること。herdr 未導入時は素のログインシェルへ素直に縮退すること。
- **稼働中反映。** lf の設定変更が `lf -remote source` で稼働中インスタンスにも効くこと。
- **併存の安全性。** herdr の claude 連携フックが tmux 配下では即 `exit 0` で抜け、旧環境と衝突しないこと（併存フェーズで確認）。
- 触れたのは devcontainer 側のスクリプトと設定なので、アプリのビルドには影響しない。リポジトリ全体で `pnpm typecheck` / `pnpm lint` / `pnpm format:check` が通ること。

一方で、**まだ実対話アタッチで確かめきれていない穴**がある。ここは正直に残す。

- **`blocked` の実発火。** 本リポジトリは `bypassPermissions` 運用のため、許可プロンプト由来の `blocked` が実際には出にくい可能性がある。ネイティブ状態検出の要である `blocked` を、実運用の遷移として見届けられていない。
- **iPad Magic Keyboard での `Shift + 矢印`。** 母艦は iPad + Blink から SSH で入ることが多い。tmux 時代から気にしていたこのキーの実配信・操作感を、herdr でまだ体感確認できていない。

前作が「検証」を丁寧に書いたぶん、続編で嘘をつかないことに価値がある。**通したところと未確認を分けて書く**のが、一次情報として残す作法だと考えている。

## Copilot に守る側のコードを詰めさせる

移行の PR（#115）は、Copilot レビューを**9 ラウンド**回して固めた。ガードレールや起動スクリプトのような「守る側／土台のコード」ほど機械の目が効く——[lefthook 回](/logs/migrating-git-hooks-to-lefthook)と同じ学びが、ここでも当たった。対応した主なものだけでも：`HERDR_VERSION` の env 化、空 ID ガード、`json_field` / workspace grep の空白許容、`set -e` 耐性2件、PATH 非依存の起動、`grep -qwF` 化、自己修復のリグレッション、File タブの lf ガード、cwd を repo ルートに固定、`ws_id_by_label` のラベル一致必須化。一方で tar.xz に関する指摘は、実証のうえ誤検知として却下した——**機械の指摘も、鵜呑みにせず裏を取ってから容れる／退ける**。

## 学び

- **「薄い自前 vs 専用ツール」の答えは、環境とツールの成熟度で反転する。** 判断の軸（この環境で確実に動くか）を固定しておけば、翌週に逆の結論を出しても筋は通る。矛盾を恐れて過去の自作に縛られないほうが、良い作業場に着く。
- **作った資産を捨てる勇気は、性質を言語化してあれば取れる。** 前作で通知機構の狙い（割り込まない・依存を足さない・視界の端に常駐する）を言葉にしていたから、それを herdr のサイドバーが満たすと分かった瞬間、迷わず捨てられた。
- **移行は段階に切ると、勢いで壊さずに済む。** PoC → オプトイン併存 → 既定切替 → 撤去。各段で戻せる状態を挟んだから、「作った翌週に捨てる」という速さと安全を両立できた。
- **pre-1.0 に乗るなら、ピンと状態永続の理解を込みで乗る。** バージョン固定は最低条件。ディスク永続は自己修復設計の前提。ツールの「らしさ」を理解して初めて、日常運用を預けられる。
- **設定は「した」と「反映された」が別。** 起動時の冪等生成に加えて、稼働中インスタンスへの波及（`lf -remote source`）まで見て、ようやく設定変更が閉じる。

