数日前から、Claude Code を立ち上げるたびに画面の上端で chrome-devtools MCP が "still connecting" の文字を点滅させたまま止まっていた。mcp__chrome-devtools__* のツールは一つもロードされず、記事のスクショを撮ろうとして呼び出すたびに空振りする。最初は「ああ、また Chrome 136+ のあれか」と決めつけて放置していた。今日その思い込みを一枚ずつ剥がしていったら、まったく別の犯人にたどり着いた。
思い込み: 「136+ のデフォルトプロファイル制約だろう」
過去に同じ症状で詰まったとき、原因は Chrome 136 以降のデフォルトプロファイル制約だと突き止めていた。デフォルトの User Data ディレクトリでは --remote-debugging-port が無効化される、というやつだ。issue にも「方法3(chrome://inspect の許可フロー)で解決済み」とメモしていた。
だから今回も同じだと思い込んだ。「解決済みとメモしたのに、なぜまた繋がらないのか」と首をひねりながら、半ば諦めて数日を過ごしていた。これが最初の一枚目だった。
一枚目を剥がす: ポートは生きているのに DevTools が死んでいた
腰を据えて原因を調べさせた。まず 9222 の状態を確認する。
curl -s --max-time 5 http://localhost:9222/json/version
正常なら Chrome/xxx とバージョン情報、そして webSocketDebuggerUrl の入った JSON が返ってくる。ところが返ってきたのは HTTP 404 と空応答だった。netstat で見ると 9222 では確かに chrome.exe が LISTENING している。
ポートは listen しているのに、DevTools エンドポイントとしては機能していない。「ポートだけ生きていて中身が死んでいる」ゾンビ状態だ。ここで「136+ の制約とは別物だ」と気づいた。制約の話ならそもそもポートが開かない。開いているのに 404 を返すのは、別のレイヤーの問題だ。
二枚目を剥がす: 残骸の9222リスナーが居座っていた
決定的な証拠は次の確認で出た。--remote-debugging-port を付けて起動中の Chrome を数えさせたら、1つも無かった。
powershell -Command "Get-CimInstance Win32_Process -Filter \"Name='chrome.exe'\" | Where-Object { $_.CommandLine -like '*remote-debugging-port*' }"
正規のデバッグ Chrome は一つも動いていない。なのに 9222 だけが壊れた状態で開いている。つまり、前にデバッグ用に起動した Chrome を閉じ損ねて残った「壊れた残骸の9222リスナー」が居座っていたのだ。
これが真因だった。--autoConnect の設定が悪いわけでも、Chrome のバージョンが悪いわけでもない。起動時に MCP サーバーがこの壊れた 9222 を掴みに行って、繋がらないまま "still connecting" で固まる。設定ファイルを一文字も触る必要はなかった。
二日間、見当違いの方向を疑っていたことになる。違和感の正体は「設定」ではなく「ゴミの居座り」だった。
クリーンな9222を立て直す
真因が分かれば手順は素直だ。Claude Code は Chrome に依存していないので、全 Chrome を落としてもエディタは生き残る。
# 全Chromeを終了して9222を解放
taskkill //IM chrome.exe //F
# temp profile + 9222 でクリーンなデバッグChromeを起動(136+ の制約に当たらない)
chrome.exe --remote-debugging-port=9222 --user-data-dir="C:/Users/numbe/AppData/Local/Temp/chrome-claude-profile"
再度 curl http://localhost:9222/json/version を叩くと、今度は Chrome/148.0.7778.179 と webSocketDebuggerUrl 入りの JSON が返ってきた。9222 が正常な CDP エンドポイントとして蘇った。ユーザーの普段のタブは別インスタンスで復元させた。
chrome.exe --restore-last-session
temp profile のデバッグ Chrome と通常 Chrome は別インスタンスなので、9222 を奪い合わずに共存する。
三枚目を剥がす: 「全体再起動が必須」も思い込みだった
ここで正直に立ち止まった。この時点で直ったのは「9222 が正常になった」ことだけで、固着していた MCP サーバーが実際に復活するかは未検証だった。だから「9222 はクリーンにしたが、MCP が直るかはまだ確認できていない」とそのまま伝えた。分かったふりをして断定するのが一番危ない。
検証していくと、MCP サーバーの挙動が見えてきた。サーバーはセッション起動時に一度だけ Chrome に繋ぎに行く。古いセッションは「9222 がまだ壊れていた時刻に起動」したので失敗し、そのまま固着して再試行していなかった。この観測から「だから Claude Code 全体の再起動が必須だ」と issue に書いた。
これがまた断定しすぎだった。
実際に試したら、/mcp メニューで chrome-devtools を reconnect するだけで mcp__chrome-devtools__* が復活した。全体再起動は要らなかった。クリーンな 9222 を用意してから reconnect すれば、その場で正常な 9222 を掴み直す。固着していた古いセッション側でも reconnect 一発で蘇った。
復活したツールで、実際に統合日記ページを開いてスクショまで撮れた。ここまで通って完全実証になった。「起動時にしか繋ぎ直さない」という観測は、reconnect という明示操作には当てはまらなかったわけだ。
後始末とナレッジ化
再発したときに迷わないよう、痕跡を残した。
- issue メモ
.claude/issues/2026-05-26-chrome-devtools-mcp-connect.mdの冒頭に「別セッションに渡せばそれだけ読んで復旧をテストできる」自己完結したクイックスタート手順を整備した。 apps/web/CLAUDE.mdに「### 5」として最頻の詰まりを追記した。~/.claude/rules/windows.mdにも同じ学びを足した。- issue はクローズ済みにした。
念のため issue メモを codex(gpt-5.5)にレビューさせたら、「因果を断定しすぎ」という妥当な指摘が2点出てきた。「autoConnect が壊れた 9222 を掴んだ」は直接ログを取っていないので有力仮説どまりだし、--browserUrl に切り替えても壊れた 9222 には繋がってしまうので「誤接続を防ぐ効果はない」という指摘だった。どちらももっともだったので、観測事実と仮説を分けて書き直した。AI のレビューに自分の断定を削らせた格好だ。
恒久対策はシンプルに落ち着いた。デバッグ Chrome(temp profile / 9222)は使い終わったら必ず閉じる。残骸を残さなければ固着の連鎖は始まらない。最後に temp profile のデバッグ Chrome だけを狙って終了して 9222 を解放し、通常 Chrome のタブはそのまま残した。
学びメモ
- 思い込んだ制約と実際の真因は別物だった。「136+ のデフォルトプロファイル制約」だと決めつけて二日放置したが、真因は「閉じ損ねた残骸の9222リスナーが居座っていた」こと。過去に解決済みとメモした件と症状が似ていても、別レイヤーで詰まっていることがある。
- ポートが listen していても endpoint が生きているとは限らない。
netstatの LISTENING だけ見ても判定できない。curl /json/versionがwebSocketDebuggerUrl入りの JSON を返すかまで確認して初めて「生きている」と言える。404・空応答ならゾンビ。 - 断定しすぎず、実証する。「全体再起動が必須」と書いたが、実際は
/mcpの reconnect だけで足りた。未検証のものは「確認できていない」と正直に言い、手を動かして実証してから言い切る。 - 観測事実と仮説を分けて書く。codex に「因果を断定しすぎ」と指摘されて初めて、自分が観測(9222 が 404)と推定(autoConnect が掴んだ)を混ぜて「確定」と書いていたことに気づいた。
- 人間は違和感を拾う係、AI は実行する係。「また固まっている」という違和感と「9222 が 404 なのは別物だ」という判断は自分が拾い、調査・クリーン化・検証・ナレッジ化の手順は AI に回させた。役割を分けると詰まりが早く解けた。
- 残骸を残さないのが恒久対策。デバッグ用に開いた Chrome は使い終わったら必ず閉じる。これさえ守れば、そもそもこの固着は起きない。