關於這個 mod
透過 WebSocket 橋接,即時監看新 Pull Request 的 CI 狀態。
本作品由社群作者維護。功能、版本需求與設定方式,請查看下方原始 README。本站提供權限掃描結果;安裝前請檢查原始碼與外部相依套件。
透過 WebSocket 橋接,即時監看新 Pull Request 的 CI 狀態。
透過 WebSocket 橋接,即時監看新 Pull Request 的 CI 狀態。
本作品由社群作者維護。功能、版本需求與設定方式,請查看下方原始 README。本站提供權限掃描結果;安裝前請檢查原始碼與外部相依套件。
請先查看作者 README,確認 marketplace 與外掛名稱;指令可能隨儲存庫結構而變動。
claude plugin marketplace add ippoan/gh-actions-live claude plugin install pr-bridge-watch
GitHub の repo の Actions ページを起点に、実行状態をポーリングせず受け取る Chrome 拡張。 repo ごとの列 + run カードで並べるので、同一 repo で並列に走っている run が全部同時に見える。
1. GET https://github.com/<owner>/<repo>/actions (cookie 付き) = スナップショット
├─ .Box-row[id^=check_suite_] → 全 run (畳まれていない)
├─ link[rel=shared-web-socket] → alive の socket URL
└─ data-channel → 署名済み購読トークン
2. wss://alive.github.com/_sockets/u/<userId>/ws?session=...
→ {"subscribe": {"<トークン>": null}} → {"e":"ack","off":"...","health":true}
**この socket は github.com のタブ (content script) で張る。** 拡張ページから張ると
Origin が chrome-extension:// になり alive が握手直後に 1006 で切る。
background が監視対象 repo の Actions ページを pinned タブで開いて場所を用意する
(pinned タブを閉じても / 他サイトへ移しても background が即ダッシュボードへ `closed` を伝え、数秒で開き直す #36)
3. push が来たら、その run の partial (/actions/workflow-run/<checkSuiteId>, 約 10KB) だけ
取り直して状態を確定する
別の API を叩かないのが要点。Actions ページの取得そのものがスナップショットなので、 初期状態を取るための追加エンドポイントは要らない。
3 は GitHub 自身のクライアント (js-updatable-content + data-url) と同じ方針で、
未文書な push ペイロードの形に依存しないための作り。
CI 状態を集約して WebSocket で配信するダッシュボードは珍しくないが、その手の実装は たいてい repo あたり「最新の in-progress」と「最新の completed」だけに畳んでいる。 一覧としては見やすいが、同一 repo で複数の workflow が並列に走るとそれらが 1 件に潰れる。
同じ tag に対して CI と複数の deploy worker が同時に走るような構成では、
畳まれた側からは 1 本しか見えない。潰れないのは GitHub 自身の alive socket
(check_suites:<id> 単位) のほうで、本拡張がそちらを直接読む理由がこれ。
なお webhook 経由の集約は取りこぼしが起きる。実運用の集約基盤が 「一定時間 in_progress のまま残った run を API から取り直す」補償を持っているのは そのためで、webhook は「来たら早い」が「来ないことがある」経路だと考えたほうがよい。
| | タブに JS を挿す | Rust / Worker | 拡張 |
|---|---|---|---|
| タブを開いたままにする必要 | 要る | 不要 | 不要 (このウィンドウが本体) |
| セッションを外に持ち出す | 不要 | user_session を貼る必要あり | 不要 |
| 変化を外に push できる | しにくい | できる | できる |
user_session は 2FA を素通りするアカウント全権の資格情報なので、ブラウザの外に出さないのが要点。
拡張は「ブラウザの中」なので、host_permissions があれば fetch に cookie が自動で付く。
| | ログイン有 | 無認証 |
|---|---|---|
| data-channel トークン | 26 個 | 26 個 (貰える) |
| link[rel=shared-web-socket] | あり | 無し (HTML に alive の言及 0 件) |
| partial /actions/workflow-run/<id> | 200 / 10.9KB | 404 |
| フルページ | 200 / 440KB | 200 / 440KB |
→ push はログイン機能。無認証でできるのは HTML / atom のポーリングまで。
ツールバーのアイコンをクリックすると、タブバーもアドレスバーも無い独立ウィンドウで開く。 設定画面から 独立ウィンドウ / 最大化 / 全画面 / タブ を選べる。
chrome.windows.create({ url, type: 'popup', width: 1280, height: 820 }) // アプリ風
chrome.windows.create({ url, type: 'normal', state: 'maximized' }) // 最大化
chrome.windows.create({ url, type: 'popup', state: 'fullscreen' }) // 全画面
このウィンドウが fetch も WebSocket も描画も全部持つ。service worker は ウィンドウを開くのと通知を出すだけなので、MV3 の service worker 寿命問題に当たらない。
MSI は 1 つで、既定は perUser (admin 不要)。管理端末向けの機能は ALLUSERS=1 を付けたときだけ出る。
| 端末 | コマンド | 何が起きるか |
|---|---|---|
| 非管理 (Windows Home / WORKGROUP / 個人 PC) — こちらが既定 | msiexec /i gh-actions-live-x.y.z-x64.msi (ダブルクリックでも同じ) | %LOCALAPPDATA%\Programs\gh-actions-live\extension に配置 + 自動更新タスクを登録。Chrome への初回読み込みだけ手動 |
| 管理端末 (AD / Entra / Chrome Enterprise Core 登録済み) | msiexec /i gh-actions-live-x.y.z-x64.msi ALLUSERS=1 (UAC 昇格) | C:\Program Files\gh-actions-live に配置 + HKLM に ExtensionSettings (force_installed)。Chrome 再起動だけで入り、更新も Chrome が拾う |
| 非管理端末を Chrome Enterprise Core に登録したい | … ALLUSERS=1 ENROLLMENTTOKEN=<token> | 上に加えて CloudManagementEnrollmentToken を書く。token は Google Admin で発行 |
なぜ分かれるか: Chrome の公式仕様で、Windows では Web Store 外 の拡張を force_installed できるのは
管理端末だけ。非管理端末ではレジストリに正しく書けていても Chrome が黙って捨てる
(chrome://policy に [BLOCKED] + 警告で出る)。詳細は #9。
自分の端末は dsregcmd /status で分かる (AzureAdJoined / DomainJoined / EnterpriseJoined が全部 NO なら非管理)。
Releases から gh-actions-live-*-x64.msi を落とし、
設定ごと インストールする (admin 不要):
msiexec /i gh-actions-live-x.y.z-x64.msi REPOS=owner/repo,owner/repo2 BRIDGEURL=ws://host:8799 NOTIFY=0
property は全部任意。渡した値は extension\config.json に書かれ、拡張が起動時に取り込む。
設定画面での手入力は不要。拡張を入れ直して chrome.storage が消えても config.json から戻る。
あとから変えたいときは同じコマンドを渡し直す (upgrade で上書き)。
ダブルクリックで入れた場合は設定画面から手で入れる (従来どおり)
chrome://extensions → 右上「デベロッパー モード」ON → 「パッケージ化されていない拡張機能を読み込む」→
%LOCALAPPDATA%\Programs\gh-actions-live\extension
ID が oaadakmclelmnaieokjbhldfacfckaaj になっていることを確認。その Chrome プロファイルで GitHub にログインしていること
以降の更新はダッシュボードの「更新」ボタン 1 クリック。MSI を入れ直す必要は無い。
Chrome は Web Store 外の拡張の update_url を相手にしないので自前で組む。拡張はディスクに
書けないため、MSI が小さな native messaging host (host.bat → host.ps1) を
HKCU\Software\Google\Chrome\NativeMessagingHosts に登録する (Policies 配下ではないので admin 不要)。
v0.0.12 → v0.0.13 あり と出たら 「更新」ボタンを押すupdate.ps1 を実行: update.xml の版を見て
gh-actions-live-extension.zip を落とし、sha256 を照合して extension\ を差し替える (ログは update.log)reloadSelf(): 先にダッシュボードのタブを about:blank に差し替えてから
chrome.runtime.reload()。reload 後は同じタブにダッシュボードを読み込み直す
(何もせず reload すると空ウィンドウが残る → #30。閉じて開き直すと新規ウィンドウが
前面に出ない → #32)Linux 側 (bridge) から {"command":"update"} を送っても同じことが起きる。
定期的に勝手に上げたい人は update.ps1 -Register でタスク スケジューラに登録できる (既定では登録しない)。
MSI を使わずに zip を展開して読み込むこともできる。その場合は host が無いので、 新しい zip を同じフォルダに上書き展開して拡張カードの ↻ を押す。
拡張の再起動 (chrome.runtime.reload()) はページを殺すだけでウィンドウは閉じないので、
放っておくと空のウィンドウが 1 枚残る (#30)。かといって先にタブを閉じると、復活が新規ウィンドウに
なり、reload 直後の service worker からでは Windows の foreground lock で前面に出ない (#32)。
reload は background の reloadSelf() に一本化してあり、先にダッシュボードのタブを
about:blank に差し替えてから reload し、onInstalled で同じタブに読み込み直す。
実機で見るのはここ:
{"command":"update"} / {"command":"reload"} を送っても同じことALLUSERS=1 を付けて実行 (UAC 昇格が 1 回) → Chrome を再起動。それだけで入る。
HKLM の ExtensionSettings は update_url が releases/latest/download/... の固定 URL なので、
版が上がってもポリシーを書き換えなくてよい。Chrome に「組織によって管理されています」が出て、
ユーザーは拡張を Chrome から削除できない (アンインストールは「アプリと機能」から MSI を消す)。
HKCU\Software\Policies はユーザー権限で書けないため、ポリシー系は ALLUSERS=1 (HKLM) のときだけ出る。
chrome://policy に ExtensionSettings が載っていない → ALLUSERS=1 で入れたか。
reg query "HKLM\SOFTWARE\Policies\Google\Chrome" /v ExtensionSettings で単一の REG_SZ に JSON が入っているか。Chrome は再起動したか[BLOCKED] + 警告 → 端末が非管理。仕様なので perUser (既定) の手順を使う%LOCALAPPDATA%\Programs\gh-actions-live\update.log を見る。
「native host が見つかりません」なら MSI で入れていない (zip 展開) か、
reg query HKCU\Software\Google\Chrome\NativeMessagingHosts\jp.ippoan.gh_actions_live が無いバージョンは自動採番する。直近の v* tag の patch を +1、tag が無ければ 0.0.1 から。
main への push で自動的に次の版を採番し、vX.Y.Z の Release を作る (Latest)ippoan/ci-workflows/.github/workflows/auto-merge.yml) が squash merge を queue する。
PR を出して緑になれば、そのまま新しい版が公開されるdev-<run_number> の prerelease を PR head に打つ。
merge を待たずに MSI を試せる。 版は「その PR が出す予定の stable と同じ」workflow_dispatch で
実行し、version に X.Y.Z を指定するtag は Release 作成時に打たれる。手で tag を push する経路は持たない (二重採番になるため)。 manifest の version は tag から stamp されるので、repo に入っている値は開発用の目安。
extension/ MV3 拡張本体 (これを Chrome に読み込む)
installer/main.wxs MSI (WiX v4+、perUserOrMachine)。配置 + 自動更新タスク + (ALLUSERS=1 で) Chrome ポリシー
installer/update.ps1 更新スクリプト (native host / 手動 / 任意でタスク登録)
installer/host.ps1 native messaging host (「更新」ボタンの実体)
bridge/ Claude Code (Linux) 側の常駐リレー (Rust / systemd --user。下記)
mods/pr-bridge-watch Claude Mod: PR を作ったら branch の CI を bridge の /watch に繋ぐ (下記)
.claude-plugin/ 上の mod を配る plugin marketplace
ws.onmessage を絞り込める。data-channel のトークンは発行時刻 t 入りの時限。20 分ごとにページを取り直して更新する。拡張は externally_connectable で https://github.com/* からのメッセージを受ける。
Claude in Chrome は github.com のタブで JS を実行できるので、設定画面を触らずに
そこから設定を流し込める (bridge URL が未設定でも届く):
// github.com のタブで
chrome.runtime.sendMessage('oaadakmclelmnaieokjbhldfacfckaaj',
{ command: 'set-config', repos: ['owner/repo'], bridgeUrl: 'ws://host:8799', notify: false },
r => console.log(r));
// command: get-config / open-dashboard / access-login / update / check-update / native-ping / status / alive-reset / reload も同じ経路で使える
拡張は GitHub を見ているだけなので、そのままでは Claude Code (別マシン) に何も届かない。
bridge/ (Rust の常駐サーバー gh-actions-bridge) を Linux 側で 1 本だけ 動かし、拡張からそこへ
outbound で WebSocket を張ると双方向になる。Claude Code の各セッションは bridge に繋ぐだけで、
bridge 自体を起動しない (セッションが Monitor で起動していた頃は 8799 を取り合い、起動したセッションが
終わると bridge ごと落ち、全 repo の変化がそのセッションにだけ流れていた)。
Windows Chrome 拡張 ──ws://<linux>:8799/?role=extension──▶ gh-actions-bridge (systemd --user)
▲ │ ws /watch?repo=…&run=…
└──── {"type":"command",...} ◀── POST /cmd ▼ (条件に合う run だけ、1 フレーム 1 行)
Claude Code の各セッション (Monitor の ws ソース)
導入 (Linux 側、1 回だけ):
cargo install --path bridge
cp bridge/gh-actions-bridge.service ~/.config/systemd/user/
systemctl --user enable --now gh-actions-bridge
bridge 自体の出力 (全 repo の変化・接続ログ) は journalctl --user -u gh-actions-bridge -f。
見張り (/watch): セッションごとに条件を付けて繋ぐ。一致した run の変化だけがテキストで届く
Monitor({ ws: { url: "ws://127.0.0.1:8799/watch?repo=ippoan/rust-alc-api&workflow=CI&run=1619" }, ... })
条件は repo / ref / run / workflow (部分一致・大小無視) / by。同じ key を重ねると OR、違う key は AND。
条件なしは 400 (全量が流れるのを防ぐ。本当に全部見るなら all=1)
run 指定の run)。
既に終わった run を指定しても待ちぼうけにならないrun を指定すると、その run が全部終わった時点で bridge が socket を閉じる (= Monitor の見張りが終わる)。
run 番号は workflow ごとなので workflow と併用する。re-run は同じ番号で走り直すので、閉じた後に re-run したら繋ぎ直すref は完全一致 (拡張が 40 文字で切るので長い branch 名は先頭 40 文字で比べる)。ref だけの見張りは閉じない生の JSON が欲しい購読者は /?role=listener (繋いだ時点で bridge の写しを snapshot として 1 回受ける。送った JSON は拡張へのコマンド)
Linux → 拡張 (設定): curl -X POST localhost:8799/cmd -d '{"command":"set-config","repos":["owner/repo"],"notify":false}'
で repo を変えられる。get-config で現在値を返す。repos は全セッション共通なので、
自分の PR に絞るのは set-config ではなく /watch の条件で
Linux → 拡張: curl -X POST localhost:8799/cmd -d '{"command":"open-dashboard","mode":"popup"}'
でウィンドウを遠隔で開ける。refresh / snapshot も受ける
Cloudflare Access のログイン承認: curl -X POST localhost:8799/cmd -d '{"command":"access-login","url":"<cloudflared access login が出した URL>"}'
で承認ページを前面のウィンドウに開く。トークンを取りに行くのは Linux 側の cloudflared 自身なので、
ブラウザは「Approve」を押すだけでよく、Windows の Chrome で承認しても通る。
bridge には認証が無く、8799 に届く者が Chrome で任意のページを開けると capability の穴になるので、
開ける URL は 3 条件を全部満たすものだけ:
https:accessLoginHosts に完全一致 (後方一致・ワイルドカードは無し。
endsWith('.ippoan.org') 型は evil-ippoan.org のような取り違えを招くため)。
未設定・空配列なら全部拒否 (deny-by-default) — 設定を入れ忘れた環境が一番危険になる既定は採らない/cdn-cgi/access/cli ちょうど (new URL() が .. を畳んだ後の値で完全一致)加えて userinfo 付きの URL は無条件で拒否する。https://[email protected]/...
は文字列としては正規のホストに見えるのに実際に開く先は evil.example.com で、
URL.host に userinfo は入らないためホスト検査だけでは防げない (なりすまし)。
許可ホストの設定 / 確認:
curl -X POST localhost:8799/cmd -d '{"command":"set-config","accessLoginHosts":["dtako.ippoan.org"]}'
curl -X POST localhost:8799/cmd -d '{"command":"get-config"}' # accessLoginHosts が返る
弾いた URL はログにも応答にも全体を出さない (token= の nonce が乗っている。出すのはホスト名とパスまで)
診断: curl -X POST localhost:8799/cmd -d '{"command":"status"}'。ダッシュボードからは
{"type":"status", alive:{connected, fails, lastState, lastMessageAt, idleMs, idleLimitMs, idleResets, watchdogArmed, idleArmed, reconnectPending, background:{state, lastMessageAt, idleMs, relay:{readyState, tokens, lastFrameAt, sinceLastFrameMs, frames}}}}、
service worker からは {"type":"ack","command":"status", alive:{tab, state, lastMessageAt, idleMs, relay}} が返る
(relay.readyState は 0=CONNECTING 1=OPEN 2=CLOSING 3=CLOSED、null = socket 無し)
connected:true は「生きている」の証明にならない (#28)。half-open (TCP が静かに死ぬ) だと
readyState は OPEN のままで ws.send() も通るので、購読し直しは成功したように見える。
生死は フレームを受け取った時刻 で見る:
alive.idleMs (ダッシュボードが最後にフレームを受けてからの ms) と
alive.background.relay.sinceLastFrameMs (socket を持つ github.com のタブ側の実測)。
idleMs が idleLimitMs (既定 10 分) を超えるとダッシュボードが自分で close → connect で張り直し、
idleResets が 1 増える。ダッシュボードのヘッダにも「最終受信 HH:MM:SS」として出る
強制再接続: curl -X POST localhost:8799/cmd -d '{"command":"alive-reset"}'。relay の socket を閉じ、
ダッシュボードが張り直す (閉じていれば開く)。status が connected:false のまま戻らないときに
拡張側は 設定画面の「Linux 側リレーの URL」 に ws://<host>:8799 を入れる
service worker も 1 本張っていて、ダッシュボードが閉じていても open-dashboard を受けられる
(リレーの 20 秒 ping → pong の往来で MV3 の service worker が生き続ける)
認証は無い。tailnet / LAN 内で使う前提。外に出すなら前段に Access 等を置く
mods/pr-bridge-watch は Claude Code の function hooks (製品名 Claude Mods、early access) で書いた plugin。
Bash の tool.call を包み、gh pr create / pr-push.sh が成功して PR の URL が出たら:
gh pr view <url> --json headRefName で branch を取る (失敗したら command の --head)GET / で生死を見る$.tool.call({ tool: 'Monitor', ws: { url: 'ws://127.0.0.1:8799/watch?repo=…&ref=<branch>' }, persistent: true })bridge が落ちている / Monitor が拒否されたときは張らずに、自分で張る Monitor(...) の引数を context に書く
(見張りが黙って欠けるより model に拾わせる)。同じセッションで同じ branch は二度張らない。
張らない環境が 2 つある。どちらも「自分で張れ」は返さず、1 行の事実だけを context に足す:
CLAUDE_CODE_ENTRYPOINT が claude-desktop)。app 側が PR の CI を
監視していて、model が Monitor で CI を見張ることを禁じているため。bridge も gh pr view も叩かないgave no verdict)。
model に張らせる指示は出さず、「CI は PR の監視か gh pr checks で確認する」とだけ返すMonitor は archive の前に止める。 ref の /watch は bridge が閉じないが、archive_session は生きた
background task を持つセッションを畳まない (still has live work)。放っておくと見張りが archive を塞ぐので:
git --git-dir=<共通 dir> show-ref refs/heads/<ref>) が消えていたら TaskStop。
PR は親セッションが作るので、子の archive → worktree-janitor で branch が消える → 親に次の通知が来た時点で止まる。
gh の polling や timer は持たないarchive_session { session_id: "self" } の tool.call を包み、先に全部の Monitor を止めてから通す[pr-bridge-watch:stop] 入りの send_message を送って
3 秒後に 1 回やり直す。受けた側の mod は session.receive (届かなければ prompt.submit) で要求を飲み込み
(turn を起こさない)、全部止める。子が自分で PR を作ったときの経路。pinned 等の拒否ではやり直さないtask ID (Monitor の結果) が取れないとき・PR の branch が local に無いときは自動では止まらないので、context にそう書く。 installed plugin は起動中のセッションでは読み直されない (更新は新しいセッションから効く)。
導入 (Claude Code 2.1.260 以上。function hooks は既定 off):
# ~/.claude/settings.json の env に "CLAUDE_CODE_ENABLE_FUNCTION_HOOKS": "1" (起動中のセッションにも効く)
claude plugin marketplace add ippoan/gh-actions-live
claude plugin install pr-bridge-watch@gh-actions-live
bridge が別ホストなら userConfig の bridgeUrl (既定 ws://127.0.0.1:8799) を変える。
開発: 型は build ごとに生成する (commit しない。mods/*/types/ は gitignore)。
CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 claude -p "/plugin-types mods/pr-bridge-watch/types"
tsc -p mods/pr-bridge-watch/tsconfig.json
claude plugin validate mods/pr-bridge-watch
npm test # test/pr-bridge-watch.test.mjs が偽の $ で register を回す (Node 24 の type stripping で .ts を直接 import)