pr-watch-pane: CI / マージ監視を専用ペインで回す
tools/pr-watch.sh <PR> --repo <owner/repo> --merge-watch を broker tmux セッション
内の専用ペイン (name="pr-watch-<PR>") で起動する。Bash tool の background 起動は
session 寿命依存で、CI 監視のような長時間 watcher には不適(公式設計の対象外)。broker
ペイン spawn を経由することで sandbox 外・窓口セッション非依存(/clear や窓口の
context リセットと無関係)に監視が継続し、人間が tmux ペインで直接見えるので可視性も高い。
監視結果は二経路で残る:
.state/state.db events テーブル … ci_completed / pr_merge_watch_timeout の
canonical event 行(payload 形・CI_COMPLETED / PR_MERGED / PR_MERGE_WATCH_TIMEOUT
のメッセージ形は不変)。これが判定の canonical 記録。
.state/pr-watch-<PR>.log + tmux スクロールバッファ … 人間可読の生ログ二段。
peer push は best-effort: 窓口への CI_COMPLETED / PR_MERGED push は欠落しうる
(欠落する条件は .claude/skills/pr-watch-pane/references/rationale.md §8)。
待つべき正路は上記 events DB 行と可視ペインであり、push の到達を merge gate の前提にしない。
輸送層(transport)両系: 本文のツール参照は mcp__org-broker__*(org-broker)で書いてあり、
active transport が既定面ならそのまま従えばよい。輸送層で手順が変わるのは 受信モデル /
spawn 儀式 / エラー分岐 の 3 点だけで、opt-in 側の輸送層は削除せず切戻しの安全装置として
常時有効。両系の読み替え規則・契約の批准経緯・broker 固有エラーコードの一覧は
.claude/skills/pr-watch-pane/references/transport-duality.md
を参照(上記 3 分岐に入るまで読む必要はない)。
本 skill の spawn 対象は「汎用 CLI ペイン」: 起動するのは tools/pr-watch.sh
という shell コマンドであり、Claude セッション(spawn_claude_pane)ではなく
汎用 mcp__org-broker__spawn_pane を使う。したがって輸送層の spawn 儀式(broker:
folder-trust + channel sidecar の 2 段承認 / renga: dev-channel の 1 段承認)は本
skill では一切発生しない(--mcp-config / --dangerously-load-development-channels
の注入が無く、承認プロンプトが出ない)。attention watcher と同じ CLI ペイン扱いである
(.claude/skills/pr-watch-pane/references/transport-duality.md
末尾の「attention watcher は ... 2 段承認の対象外」注記と同義)。両系で
切り替わるのは ツール完全修飾名だけ:
- 既定 broker:
mcp__org-broker__spawn_pane(list_panes / set_pane_identity /
inspect_pane / close_pane も同様に mcp__org-broker__*)
- opt-in renga:
mcp__renga-peers__spawn_pane(同様に mcp__renga-peers__*)
以下の手順は mcp__org-broker__ を active transport の完全修飾名に読み替えれば両系で論理同一。
spawn 直後の identity 登録・起動コマンドも両系共通(CLI ペインなので Enter 承認は不要)。
前提
- 本 skill は 窓口(secretary)専用。汎用
spawn_pane は broker の auth tier 上
secretary だけが持つ(dispatcher は spawn_claude_pane のみ。contract
docs/contracts/backend-interface-contract.md
Surface 8 の ops tier 定義)。
- 窓口の cwd は ja-root(リポジトリ root)。spawn する pr-watch は
.state/ パスや
tools/pr-watch.sh を ja-root 基点で resolve する必要があるため、cwd trap を skill
側で吸収する(Step 1 で git rev-parse --show-toplevel の絶対パスを spawn_pane の
cwd に明示。窓口 cwd が何らかの理由で ja-root を外れていても正しく解決される)。
- 自己 close の実装は「watcher ペインが tmux ペインであること」前提。Step 3 の
command は
bash 実行と、自己 close の tmux kill-pane -t "$TMUX_PANE"(自ペインを $TMUX_PANE で明示
指定して kill。socket は $TMUX 継承をそのまま使い -L で固定しない = tmux 上に立つ
ペインであれば broker の既定 socket / 非既定 socket いずれでも正しい server に当たる。
Issue #647 提案 1 の「明示 target 指定」を実装した形)に依存する。tmux ペインでない環境では
$TMUX / $TMUX_PANE がそもそも env に無く、-t "" へ展開されて 2>/dev/null || true で
無言失敗する(下記の分類を参照)。
- 自己 close の 2 分類: 自己 close が効くのは watcher ペインの実体が tmux ペインのとき
(broker の tmux backend)だけで、それ以外では監視終了後もペインが残る。残る場合は監視終端で
窓口がイベント駆動で close する(
.claude/skills/org-pull-request/SKILL.md
の各終端処理。手順は Step 5 の (a)/(b))。分類の詳細は
.claude/skills/pr-watch-pane/references/rationale.md §9。
- Windows native の手動起動経路(人間が
tools/pr-watch.ps1 <PR> を ! 経由で起動)は
遮断しない。
Step 1: 引数解決と cwd / repo の確定
-
PR 番号 <PR> を引数から取る(必須・正の整数)。省略時はユーザーに PR 番号を尋ねて中断。
-
ja-root の絶対パスと owner/repo を決定的に解決する(cwd trap 吸収 + repo 明示):
git rev-parse --show-toplevel # -> JA_ROOT(絶対パス)
gh repo view --json nameWithOwner -q .nameWithOwner # -> OWNER/REPO
printenv ORG_TRANSPORT ORG_BROKER_STATE_DIR PATH # -> transport env(Refs #653 #658)
gh repo view が失敗する場合はユーザーに「リポジトリを自動解決できません。
--repo OWNER/REPO を指定してください」と報告して中断。pr-watch.sh 自身も --repo
省略時は auto-resolve するが、ここで明示することで pane の cwd に依らず repo を固定
する(cwd trap が repo 解決にも波及しないようにする)。
- transport env の捕捉(Refs #653 #658 — env trap 吸収):
printenv で窓口
(secretary)ペインの ORG_TRANSPORT / ORG_BROKER_STATE_DIR / PATH を確定させる。
mcp__org-broker__spawn_pane は 汎用 CLI ペインを env 注入なしで立てるため(本 skill 上部の
注記)、これらを Step 3 の command に前置注入しないと spawn されたペインの
tools/peer_notify.py が ORG_TRANSPORT 未設定で silent no-op に落ちる。
cwd が cwd trap を吸収するのと同じ理屈で、この env 捕捉が env trap を吸収する。
ORG_TRANSPORT が空 / 未設定なら Step 3 で export ORG_TRANSPORT を省く。
これは窓口ペインの解決結果をそのまま鏡写しにするための省略である(PR #73 障害の
経緯と、未設定の意味が renga → broker に変わった Refs #941 の経緯は
.claude/skills/pr-watch-pane/references/rationale.md §3)。
ORG_BROKER_STATE_DIR も set のときだけ前置する。
Step 2: 冪等チェック(同一 PR の二重監視を防ぐ)
mcp__org-broker__list_panes を呼び、name="pr-watch-<PR>" の live pane が既に存在するか確認する。
- 存在する → 既に監視中。spawn せず「PR #<PR> の CI 監視ペイン
(
pr-watch-<PR>, id={N}) は既に稼働中です」と報告して終了。再起動したい場合は先に
Step 5 の手順でそのペインを close してから再実行するよう案内する。
- 存在しない → Step 3 へ。
pane name pr-watch-<PR> が冪等キー。<PR> は数字だが pr-watch- プレフィックスで
英字を含むため許可文字 [A-Za-z0-9_-] を満たし、全桁数字(= id 扱いで曖昧化)にもならない。
Step 3: 監視ペインの spawn
Step 3-0: 張り先(アンカー)を幾何で決める(Refs #335)
spawn の前に、どのペインを split 起点にするかを決定的ツールで求める。target は
固定しない(旧版の target="dispatcher" 固定が生んだ実害の経緯は
.claude/skills/pr-watch-pane/references/rationale.md §2)。
-
mcp__org-broker__list_panes を呼び、返却された panes 配列(id / name / role /
x / y / width / height)をそのまま JSON として控える。
-
その JSON を tools/pick_watcher_anchor.py に stdin で流し、候補リストを得る
(既定入力が stdin なので中間ファイルを作らない。ファイル経由なら --panes-json <path>):
python3 tools/pick_watcher_anchor.py --format text <<'PANES'
{"panes": [ ... list_panes の返却をそのまま貼る ... ]}
PANES
(Windows は py -3 tools/pick_watcher_anchor.py ...。POSIX 側は python エイリアスが
無い環境があるため python3 で呼ぶ。transport は ORG_TRANSPORT から自動解決される
ので通常は指定不要。明示するなら --transport {broker|renga}。)
-
出力の candidates が rank 1 から順に試す張り先。各候補は target(pane name)と
direction(vertical / horizontal)を持つので、両方をそのまま次の
spawn_pane 引数に使う(direction も候補ごとに変わる。vertical 固定ではない)。
direction は「両方向を評価し、親側に残る子が大きい方を採る」規則(runtime の
_split_options と同一。同値性は unit test で固定)。窓口が direction を自分で
決めない(実害の経緯は
.claude/skills/pr-watch-pane/references/rationale.md §2)。
-
candidates が空(exit code 2 / capacity_exhausted: true)のときだけ、このタブに
watcher を置く余地が本当に無い。spawn を試みず、rejected の各行(どのペインが
どの床値でどう落ちたか)を添えて人間に報告し、指示を仰ぐ。
出力の pane_cap_reached は助言であって判定ではない: 「このタブのペイン数が renga の
per-tab 上限(RENGA_MAX_PANES = 16)に達している」という事実で、renga ではこれ以降の
spawn が幾何によらず拒否される。既定の broker には per-tab 上限が無い(各ペインが独立
セッション)ので、capacity_exhausted はこの値では動かない。[split_refused] が続いた
ときの人間報告に添える材料として使う。
ツールが守る床値(MIN_PANE 20x5 / SECRETARY_MIN 120x30 / DISPATCHER_MIN_WIDTH 80)は
claude_org_runtime.dispatcher.runner の定数を import して 使う(skill 側にも
ツール側にも値を写経しない)。優先度は dispatcher →(既存 watcher ペイン。dispatcher
隣接を先)→ secretary、dispatcher が自身の comfort 幅を割っている場合だけ最後尾へ降格。
secretary を張り先に使うのはユーザー承認済みで、SECRETARY_MIN_* を
満たす split のみ候補になる。既存 watcher には attention watcher(role="attention")も
含む。worker / curator ペインは候補に入れない(稼働中の作業ビューポートを watcher が
食わないため)。
Step 3-1: spawn
mcp__org-broker__spawn_pane で CLI watcher ペインを起動する(<...> は Step 1 / 3-0 / 引数で確定した値に置換):
mcp__org-broker__spawn_pane(
target="<Step 3-0 の候補 target>",
direction="<Step 3-0 の候補 direction>",
role="watcher",
name="pr-watch-<PR>",
cwd="<JA_ROOT 絶対パス>",
command="nocorrect true 2>/dev/null || true; export ORG_TRANSPORT='<ORG_TRANSPORT>'; export ORG_BROKER_STATE_DIR='<ORG_BROKER_STATE_DIR>'; export PATH='<PATH>'; mkdir -p .state; bash tools/pr-watch.sh <PR> --repo <OWNER/REPO> --merge-watch --no-detach 2>&1 | tee -a .state/pr-watch-<PR>.log; tmux kill-pane -t \"$TMUX_PANE\" 2>/dev/null || true"
)
-
nocorrect true 2>/dev/null || true; 前置(Issue #825 — zsh correctall 無言停止の根治。必須・置換禁止):
command は対話シェルに打鍵される形で実行されるため、pane の login shell が zsh で
スペル自動訂正(setopt correctall)が有効だと、残留 .state/pr-watch-*.log に類似した
新規ログ名が zsh: correct '...' to '...' [nyae]? の確認プロンプトを発火させ、
パイプライン全体が一度も走らないまま無言停止する。行頭の nocorrect は zsh の
precommand modifier(reserved word)で、パース前に解釈されその行全体の全 word のスペル
訂正を無効化する(; 連結の後続コマンドにも効く)。実発生ログと実機確認の記録は
.claude/skills/pr-watch-pane/references/rationale.md §4。
- payload から独立した犠牲文にすること、
2>/dev/null(Step 4 の誤殺防止)と || true
(errexit 耐性)はどれも省略不可(各要素の理由は同 §4)。
<...> placeholder ではない固定文字列であり、窓口は値に置換せずそのまま残す。下記の
条件付き前置で export 文を省いた場合も、nocorrect true 2>/dev/null || true; は
常に行頭に残す。
-
env 前置注入(Refs #653 #658 — 必須。PR #73 障害の根治): 犠牲文に続く
export ORG_TRANSPORT=...; export ORG_BROKER_STATE_DIR=...; export PATH=...; は Step 1 で
printenv 捕捉した窓口ペインの実値に置換する。これが無いと汎用 spawn ペインは transport
env を継承せず、peer_notify の broker/renga 経路がどちらも未設定分岐に落ちて push が
silent no-op になる(events テーブルへの ci_completed 書き込みは成功するのに窓口へ届かない)。
PATH 注入は broker 経路が shell out する claude-org-runtime broker send CLI(venv 内)を pane の PATH で解決可能にするため。
- 条件付き前置(窓口ペインの解決を鏡写しにする):
ORG_TRANSPORT が空 / 未設定なら
export ORG_TRANSPORT=... を丸ごと省く。未設定を渡された watcher ペインは窓口ペインと
同じ resolve() を通って同じ transport(既定は broker)に着地するので、省略は
「窓口と同じ解決結果を継がせる」ことを意味する。ORG_BROKER_STATE_DIR も
set のときだけ前置する。空値を '' で明示 export すると broker 経路が既定 state dir を
掴む誤動作を招くため、空なら export 文自体を書かない。
- この env 注入は低遅延 push の修理で、単独の保証ではない(多層防御の内訳は
.claude/skills/pr-watch-pane/references/rationale.md §3)。
-
target / direction: Step 3-0 のツールが返した候補をそのまま使う。broker では
各ペインが detached 独立セッションだが、addressing scope(同一タブ MUST、contract
Surface 4.2)を満たすため既存ペインを起点に取る点は不変。
-
role="watcher": list_panes で監視ペインを識別する表示ラベル(attention watcher の
role="attention" と同じく canonical 4 role 以外のラベル。broker では token の auth tier
は spawn 時に固定で、role ラベルは tier を変えない — Surface 8)。
-
cwd: Step 1 で解決した JA_ROOT 絶対パス。これにより pane 内の tools/pr-watch.sh /
.state/pr-watch-<PR>.log が ja-root 基点で resolve される(cwd trap 吸収)。
-
--no-detach 必須(Issue #650): tools/pr-watch.sh は Issue #641 対策で既定
setsid + nohup の自己 detach をするため、--no-detach を付けないと spawn 直後に親 bash が
exit して broker pane が即時掃除され、watcher が孤児化する(pane が無いので /org-attach /
Ctrl-b s でも覗けない)。--no-detach で前景動作させ、tee と末尾 self-close の
自己終了サイクルを成立させる。Ctrl-b s で覗けないときは
docs/operations/dispatcher-view.md §4 を参照。
-
command: pr-watch を前景実行し、stdout/stderr を .state/pr-watch-<PR>.log に tee -a
(tmux スクロールバッファにも出る二段)。先頭 mkdir -p .state で fresh clone でも tee の
出力先を確保する。pr-watch 終了後に tmux kill-pane -t "$TMUX_PANE"
で ペインを自己 close(broker の tmux backend でのみ効く低遅延経路。|| true は tmux ペイン
でない環境 = herdr / wezterm / renga で失敗を握り潰すためのもので、そこでは自動 close されず
窓口のイベント駆動 close が正路になる。前掲「前提」節の 2 分類)。
$TMUX_PANE は placeholder ではなく実行時 env: <PR> / <OWNER/REPO> と違い
$TMUX_PANE は窓口が値に置換してはならない。tmux ペインとして spawn された場合に shell へ
自動露出する自ペイン id(例 %16)で、self-close 時に その pane 自身を明示 target 解決する。
旧形 tmux kill-pane(target 無指定)は現在ペインを暗黙推定するが、-t "$TMUX_PANE" で
明示することで曖昧さを排す(Issue #647 提案 1 の「明示 target 指定」)。
- tmux ペインでなければ未設定で無言失敗する(Issue #911): renga のペインでは
printenv TMUX TMUX_PANE がどちらも未設定を返すため、この行は tmux kill-pane -t "" に
展開され 2>/dev/null || true で無言失敗する(herdr / wezterm も同様に tmux ペインでない)。
この失敗はエラーとして表に出ないので、残留の検知を self-close の成否に依存させない。
- socket は
$TMUX 継承をそのまま使う(-L で固定しない)(理由は
.claude/skills/pr-watch-pane/references/rationale.md §10)。
- self-close は(効いた場合も)tmux 層だけを掃除する:
kill-pane は broker socket 上の実 tmux ペイン
を消すので、直後から list_panes には現れなくなる。ただし broker daemon の pane 登録簿
(name binding)は self-close では pop されず stale に残りうる(daemon は自 pane の外部
kill を検知しないため)。stale binding が残ると同名 pr-watch-<PR> の再 spawn が
[name_taken] で弾かれる(list_panes には出ないのに、が症状)。この掃除は Step 5 の
手動 fallback で行う。そこで撃つ mcp__org-broker__close_pane(target="pr-watch-<PR>") は、
数値 pane_id を列挙から取り直せないこの状態に限って裸 name を許可する transport 条件付きの
allowlist 経路であり、無条件の name 指定ではない(3 条件と broker 以外での扱いは Step 5 の
(b) が SoT)。
--merge-watch: CI green で CI_COMPLETED を出した後もマージまで poll し続け(最大
24h)、マージで PR_MERGED、timeout で PR_MERGE_WATCH_TIMEOUT を出してから自己 close を
試みる。CI 確定だけで止めたい場合は呼び出し時に --merge-watch を外す(その場合は CI green
/ failed 確定で自己 close を試みる)。いずれも実際に閉じるのは broker の tmux backend のみで、
herdr / wezterm / renga では窓口のイベント駆動 close が掃除する。
- 人間が
! 経由で tools/pr-watch.sh を手動起動する経路も動作する。
戻り値 "Spawned pane id=N." の N を控える。
spawn 失敗時の分岐(MCP 結果テキストの [<code>] で判定。詳細は
.claude/skills/org-delegate/references/renga-error-codes.md):
[split_refused] → 候補リストを rank 順に 1 段ずつ降りて再試行する(各候補は 1 回だけ)。このコードは「その target を
今の幾何では割れない」というターゲット局所の失敗を含む(free-pane 不足だけでなく、
細ったアンカー 1 枚の MIN_PANE 割れでも返る)。1 件の拒否からタブ満杯と結論しない —
容量判断は Step 3-0 の幾何計算(list_panes × 床値)だけが行う:
- Step 3-0 の
candidates から次の rank を取り、その target / direction で
Step 3-1 の spawn を 1 回だけ再試行する。
- 再試行も
[split_refused] なら、さらに次の rank へ進む(candidates の末尾まで)。
レイアウトが動いた疑いがあるときは Step 3-0 を 1 度だけ引き直してよい。
candidates を使い切ったときだけ「監視ペインを作る空きがありません(ターミナルが
狭い / pane 上限)。ターミナルを広げるか不要ペインを閉じてから再実行してください」と
報告して中断する。報告には試した target 一覧と Step 3-0 の rejected 行(どの床値で
落ちたか)を添える。
[pane_not_found](候補 target が既に消えている)→ レイアウトが spawn 前後で動いた
race。Step 3-0(list_panes → ツール)を 1 度だけ引き直して最新の rank 1 で再試行する。
引き直しても候補が空 / 同じく [pane_not_found] なら、/org-start 未実行を含む想定外
として状況を人間に報告して中断。
[name_in_use] / [name_taken] → live pane と stale 登録簿を切り分ける(前 watcher の
self-close で tmux ペインは消えたが broker 登録簿に name binding が残る「二層の不整合」を
自己回復する):
mcp__org-broker__list_panes で name="pr-watch-<PR>" の live pane が実在するか再確認する。
- live pane が在る → 冪等チェックを取りこぼした真の race。既に監視中として Step 2 の
「既に稼働中」報告に倒す(新規 spawn しない)。
- live pane が無い(
list_panes に出ない)→ self-close 済みの stale 登録簿 binding。
この状態は列挙にペインが出ないため 数値 pane_id を取得できず、Group B の
「数値 pane_id で撃つ」原則をそのまま適用できない。よってこの掃除だけは transport 条件付きの
allowlist として裸 name の close_pane を許可する。3 条件と broker 以外での扱いは
下記 Step 5 の (b) が SoT で、それに従う:
- (b) の 3 条件が揃う場合:
mcp__org-broker__close_pane(target="pr-watch-<PR>") で name 解決させて登録簿を
pop し(ok closed=%N が返る。[pane_not_found] は既に掃除済みで OK)、Step 3 の spawn を
1 度だけ再試行する。再試行でも [name_taken] が続く場合はユーザーに報告して中断
(想定外の登録簿状態)。
- 揃わない場合(broker 以外に解決する / 確定できない): close も自動再試行もせず、
stale binding を検出した旨をユーザーに報告して指示を仰ぐ。
- broker 固有(
[no_backend] / [token_invalid] / [session_invalid] /
[tool_not_authorized] / [peer_not_found] 等)/ その他未知コード → 状況をユーザーに
報告して中断(default-branch escalate)。
Step 4: identity 登録と起動 health check
-
identity 確定(role=watcher の登録): spawn_pane の name / role が反映されたか
mcp__org-broker__list_panes で確認する。name="pr-watch-<PR>" かつ role="watcher" が付いて
いれば登録済み。万一いずれかが欠ける backend では
mcp__org-broker__set_pane_identity(target=<N>, name="pr-watch-<PR>", role="watcher") で補修する。
- 補修も Group B なので自タブ確立が先(確立手段の 2 つは Step 5 の (a) と同じ):
set_pane_identity は close_pane と同じ Group B で、宛先は「自タブと独立に確立済みの
列挙から得た数値 pane id」に限られる。数値であることは MUST の片方にすぎず、id が
spawn の戻り値として手元にあることも免除にならない(未確立の列挙では、直前の name /
role 確認の結果そのものを信用できないため)。どちらも成立しないなら補修を撃たず、
identity が未登録のまま監視が走っていること(Step 2 の冪等チェックと Step 5 の cleanup が
name / role で引けなくなること)をユーザーに報告する。相対セレクタ(target 省略 /
"focused" / 裸 name)は hook が deny する。
- 本ペインは Claude セッションではなく CLI プロセスなので Claude peer(peer_id)は
持たない。ここでの「peer 登録」= pane registry への name + role=watcher の登録
(spawn_pane / set_pane_identity が行う)を指す。MCP peer / dev-channel 登録は発生しない。
-
即時クラッシュ / 対話プロンプト停止の検出(negative-signal のみ):
mcp__org-broker__inspect_pane(target=<N>, format="text", lines=<ペイン高さ以上>, include_cursor=true)
で出力を全画面読む(Issue #825)。lines は mcp__org-broker__list_panes の geometry で分かる
ペイン高さ以上、高さが取れなければ余裕を取って lines=200 を指定する。末尾 15-40 行だけの
読み取りは、画面上部に出た確認プロンプト + 下部空白を「静かな正常起動」と誤読する
(見逃した実例は .claude/skills/pr-watch-pane/references/rationale.md
§5)。include_cursor=true は下記
「行末 ? + 入力待ち」判定に必要なカーソル位置を返させるため必須(既定 false では
カーソル情報が返らず、ブロック中のプロンプトと通常出力を区別できない)。
以下のいずれかを検出した場合のみ「起動失敗」と判定:
command not found / is not recognized / No such file or directory
gh: ... not found 等 gh 不在 / Traceback (most recent call last) / ModuleNotFoundError
- 出力末尾に shell prompt(
$ / % 末尾露出)= command が即時終了して shell に戻った
- 対話プロンプト形跡(Issue #825 で追加):
[nyae]?(zsh correctall の
zsh: correct '...' to '...' [nyae]?)/ (y/n) / [y/N] のリテラル、または行末が ? で
終わる行に cursor が乗ったまま(= その行で入力待ち)後続出力が無い。いずれもコマンドが
一度も走らないままシェル層の確認プロンプトで停止している形跡で、放置すると監視ゼロの
無言死になる(correctall 停止なら Step 3 の nocorrect 前置漏れを疑う)。
- 上記いずれも無ければ(pr-watch の watch ループ出力 / 空 / 起動直後の静止)起動成功扱い
とし、固定 sleep を入れて再 inspect する経路は持たない(健全な quiet start を誤殺しない)。
- 起動失敗時は死んだペインを掃除し、原因(
tools/pr-watch.sh / gh の導入、cwd、対話プロンプト
停止)をユーザーに報告して中断する。掃除は 自タブ確立 → identity 再確認 → 数値 close の
順で行う:
- 自タブ確立(確立手段の 2 つは Step 5 の (a) と同じ)。どちらも成立しないなら close を撃たず、死んだペイン (id=
<N>) が
残る旨を報告に含めて手動掃除に委ねる(相対セレクタへはフォールバックしない)。id が
spawn の戻り値として手元にあることは免除にならない — 未確立の列挙では次の identity
照合の結果そのものを信用できないため
- identity 再確認(pane_id recycle 対策。他の全経路と揃える):
mcp__org-broker__list_panes で
<N> のレコードを引き、name="pr-watch-<PR>" かつ role="watcher" をなお指している
ことを確かめる。別ペインへ再割当て済み / 列挙に現れない場合は close せず、その旨を
報告に含める(本 Step の「identity 確定」で補修を撃てなかった backend ではこの照合が
通らないので、同じく close せず報告に倒す)
- 両方を通ったときだけ
mcp__org-broker__close_pane(target=<N>) する([pane_not_found] /
[pane_vanished] は直前に消えた扱いで skip)
-
生存確認は副作用ベースで行う(pgrep -f 単独判定の禁止、Issue #825): 起動後に
「監視が本当に走っているか」を確かめるときは、プロセス一覧ではなく副作用を見る:
pgrep -f "pr-watch" 単独を生存判定に使わない。spawn されたペインのラッパーシェルの
argv にはコマンド全文(pr-watch.sh を含む)が乗るため、確認プロンプト停止中で
パイプラインが一度も走っていなくてもシェル自身にマッチして偽陽性になる(誤認の実例は
.claude/skills/pr-watch-pane/references/rationale.md §5)。
- 一次の副作用は
.state/pr-watch-<PR>.log の生成: Step 3 の tee -a はパイプライン
開始と同時にログファイルを open/生成するため、fresh なログ名なら「ファイルが出来ている」=
パイプラインが実際に走った確証になる(プロンプト停止ではコマンド自体が実行されないので
ログは生成されない)。同名の残留ログがある再監視では生成有無で判別できないため、上記 2 の
全画面 inspect(プロンプト形跡なし)と組み合わせて判定する。
- 判定確定の副作用は
.state/state.db events テーブルの ci_completed 等の canonical
event 行(本 skill 冒頭の二経路)。なお pr_watch.py は起動 banner を出力しないため、
ログが空のまま静止していても quiet start でありうる(誤殺しない)。
Step 5: 監査記録と報告 / 手動 close
-
journal に起動を best-effort で記録する:
bash tools/journal_append.sh pr_watch_pane_started pr=<PR> repo=<OWNER/REPO> pane_id=<N>
bash が使えない環境では Mac/Linux は python3 tools/journal_append.py pr_watch_pane_started pr=<PR> repo=<OWNER/REPO> pane_id=<N>、Windows native では py -3 tools/journal_append.py pr_watch_pane_started pr=<PR> repo=<OWNER/REPO> pane_id=<N>。
この行は監査ログではなく、watcher 生存判定の一次証拠である(Refs #978): 記録を落とすと
tools/watcher_restart_guard.py は「watcher なし」
(missing)と判定する(画面にペインが見えていてもである — ペインの存在は監視の生存を意味
しない、が同ガードの前提)。best-effort とは「失敗しても spawn 自体は巻き戻さない」の意味
であって、省略してよいという意味ではない。
記録したら、それが判定に反映されたことをその場で確認する:
python3 tools/watcher_restart_guard.py check --task <task_id> # Mac/Linux
py -3 tools/watcher_restart_guard.py check --task <task_id> # Windows native
exit 0(live)なら「最後の push より後に開始された watcher がある」ことが events の時系列
から機械的に確認できた状態。exit 3 が返る場合は 1. の記録が入っていないか、この watcher が
最後の push より前に起動している(=立て直しが必要)。cross-repo で task が引けない場合は
check --pr <PR> --repo <OWNER/REPO> でもよい(run 行から baseline を逆引きする)。
-
ユーザーに報告する:
PR #<PR> の CI / マージ監視ペイン pr-watch-<PR> (id={N}) を起動しました。
- ログ: .state/pr-watch-<PR>.log(tmux スクロールバッファにも出力)
- 監視終了(マージ / CI 失敗確定 / timeout のいずれか)で、broker の tmux backend では
ペインが自動で閉じます。herdr / wezterm backend と renga では自己 close が効かず残留する
ため、窓口が監視終端でイベント駆動 close します(org-pull-request の各終端処理)。
確定した CI 判定は `.state/state.db` の `ci_completed` 行とログに残るので、
ペインが閉じても判定は失われません(merge gate はそこを読む)。
- tmux で直接見るには `/org-attach` のコマンドを使ってください。
-
監視終端クローズ / 手動 close: self-close が効かず残ったペインの掃除と、効いた後に残る
登録簿の name binding の掃除(前掲「前提」節の 2 分類)は、下記 (a)/(b) の split に従う。窓口の
イベント駆動 close(監視終端)では、下記に加えて「spawn 時に控えた pane_id + 監視対象 head に束縛し、
終端イベントの head が追跡中 instance と一致するときだけ close する」freshness gate を必ず併用する
(終端イベントが遅延 / 重複配送されて同一 PR の watcher が既に再起動済みの場合、name で live pane を
再導出すると新 watcher を解決してその数値 pane_id を close しても replacement monitor を誤 close する。
束縛と freshness gate の SoT は
.claude/skills/org-pull-request/references/watcher-cleanup.md)。
人間が現ペインを手動 close する (a)/(b) は現 instance が対象で自明なので freshness gate は不要:
-
(a) tmux ペインが live のまま残った / 監視を途中で止めたい: まず照合に使う
mcp__org-broker__list_panes の列挙を自タブのものと確立する(契約
docs/contracts/backend-interface-contract.md
T-§4.2「Fail-safe consequence for Group B」)。確立手段は 2 つだけで、いずれか 1 つが成立
すればよい: (i) backend が Group B を自身の単一タブモデル内で解決する(org-broker。
契約 §8.1 / §8.10)/ (ii) caller_scope を確立できている(契約 T-§cap。
caller_scope_close_identity から導出しない)。どちらも成立しないなら close を撃たず、
watcher ペインが残る旨をユーザーに報告して停止する(相対セレクタへはフォールバックしない)。
確立できたら name="pr-watch-<PR>" の live pane を確認し、その 数値 pane_id で
mcp__org-broker__close_pane(target=<N>) する([pane_not_found] / [pane_vanished] は既に
閉じた扱いで skip)。name 指定ではなく list_panes で identity を確認した数値 pane_id を
使う(id recycle 時の誤 close を避ける)。live pane が列挙に出ても自タブ確立は省けない
(理由は .claude/skills/pr-watch-pane/references/rationale.md §7)。
-
(b) stale 登録簿 binding(list_panes には出ないのに再 spawn が [name_taken]):
self-close で tmux ペインは消えたが broker 登録簿に name binding が残っている状態。
ペインが列挙に出ないので 数値 pane_id を取得できず、(a) の「list_panes で identity 照合
した数値 pane_id で撃つ」形が原理的に取れない。この一点に限り transport 条件付きの
allowlist として裸 name の close_pane を許可する(本節が 3 条件と根拠の SoT。契約
docs/contracts/backend-interface-contract.md
T-§4.2。契約からこの mechanism が導かれる経緯は
.claude/skills/pr-watch-pane/references/rationale.md §6)。
以下 3 条件がすべて成立するときだけ許可される:
-
(1) いま Group B を駆動している backend が close_pane / set_pane_identity を
自身の single-tab モデル内で解決する(= org-broker)— そのモデル内で name も
解決されるため誤タブ hazard が構造的に生じない(契約 §8.1 / §8.10)。
判定は積極的な証拠でのみ行う(MUST): いま Group B を撃つのに使っている MCP ツールの
完全修飾名が mcp__org-broker__* であること。これは呼び出しの直前に必ず確定している
(mcp__org-broker__ は起動時に片方へ解決済み)ので、常に判定できる。
DEFAULT_TRANSPORT から推定してはならない(MUST NOT)。ORG_TRANSPORT 無設定は
運用既定 renga の構成でもありうるのに
tools/transport.py の resolve() は無設定を
コード既定 broker に解決するため、推定すると renga が Group B を駆動している環境で
裸 name の close を撃ち、別 org の同名 watcher を不可逆に閉じうる(失敗方向の非対称性・
二フレームとの関係・Step 1 の printenv 省略が別論点である理由は
.claude/skills/pr-watch-pane/references/rationale.md §6)
-
確定できないときは carve-out を取らない(fail-safe): ORG_TRANSPORT に未知値が
入っていて解決が ValueError になる等で「いま何が駆動しているか」を確定できない場合は、
条件 (1) を不成立として扱い、下の「broker 以外」と同じくユーザーに報告して指示を仰ぐ
-
(2) 再 spawn が [name_in_use] / [name_taken] で弾かれている — stale binding の症状
-
(3) その name が mcp__org-broker__list_panes に現れない — live pane 不在(=列挙から数値
pane_id を取れない、この allowlist が必要になっている当の条件)
-
3 条件が揃う場合: mcp__org-broker__close_pane(target="pr-watch-<PR>") する(broker が name →
stale pane_id を解決し登録簿を pop、ok closed=%N が返る。[pane_not_found] は既に
掃除済みで OK)。掃除後は同名 spawn が通る。Step 3 の [name_taken] 分岐はこの (b) を
自己回復するが、手動でも同手順で掃除できる。
-
broker 以外に解決する場合(ORG_TRANSPORT=renga の opt-in など)では裸 name に
フォールバックしない(「live pane が無いので誤 close の余地が無い」という前提が renga の
legacy 解決では偽になりうるため。機序は
.claude/skills/pr-watch-pane/references/rationale.md §7)。
この経路では close せず、stale binding を検出した旨と pr-watch-<PR> の
再 spawn が弾かれる状態であることをユーザーに報告して指示を仰ぐ。
-
控えた pane_id での close にも倒さない: list_panes で再確認できない retained id を
close に使うのは (a) が防いでいる pane_id recycle hazard を素通りさせるため、本 skill は
採らない([name_taken] が続く場合と同じくユーザー報告に倒す)。