Claude Code などから Playwright MCP を使ってブラウザを自動操作していると、「前回のブラウザが開きっぱなしのまま次のタスクを実行しようとすると、新しいウィンドウが開かず処理が進まなくなる」という現象に出くわすことがあります。この記事では、実際にインストールされている @playwright/mcp(v0.0.78)のソースコードを直接読んで原因を切り分け、孤児化したブラウザプロセスだけを安全に自動で片付ける SessionStart フックで対処した内容をまとめます。「同じウィンドウでは1つのブラウザしか動かせないのか?」という疑問にも技術的に答えます。

Playwright MCP のブラウザ再利用問題とは?

発生していた症状

結論から言うと、症状は「同一セッション内でブラウザが再利用されるはずが、前回のブラウザが機能不全のまま残り、新しいタスクが進まなくなる」というものです。

具体的には、同じ VS Code ウィンドウ上で Playwright MCP を使い続けているとき、前回起動したブラウザが開きっぱなしの状態で次のタスクを実行しようとすると、新しいブラウザウィンドウが開かず実行が止まってしまいます。一方で、別々の VS Code ウィンドウで実行すると、それぞれ独立したブラウザが問題なく動作します。この「別ウィンドウなら平気」という点が、原因を特定する重要な手がかりでした。

この記事のゴール

対応の目的は次の2つに絞りました。

  • 前回のブラウザが壊れている・孤児化しているときは自動で片付け、次回は必ずクリーンな新しいブラウザが開くようにする
  • 「同じウィンドウでは1つのブラウザしか動かせないのか」という疑問を、推測ではなくソースコードで明らかにする

「1ウィンドウ=1ブラウザ」は仕様?それともバグ?

結論:意図された設計であり、バグではない

先に答えを書くと、「1つの VS Code ウィンドウ(セッション)につきブラウザ1つ」は意図された仕様です。バグではありません。

stdio 接続の Playwright MCP は、VS Code ウィンドウ(セッション)ごとに1つの npx 子プロセスを起動し、その中で Browser インスタンスを1つだけ保持して使い回す設計になっています。これは、ログイン状態などのブラウザコンテキストをタスクをまたいで維持するための作りです。

だから別ウィンドウでの同時実行は安全

この設計を踏まえると、「別ウィンドウなら問題ない」理由もはっきりします。別ウィンドウ=別プロセス=別ブラウザなので、複数の VS Code ウィンドウで同時に Playwright MCP を動かしても互いに干渉しません。つまり症状は「同一セッション内でのブラウザの使い回し」に固有の問題であって、並行利用そのものの問題ではない、と切り分けられます。

プロファイルロック競合はもう起きない — --isolated の役割

--isolated は起動のたびにユニークな一時プロファイルを作る

過去に Playwright 系ツールでよく遭遇した「プロファイルロック競合」は、--isolated を付けている限り原理的に起きません。

MCP 設定に --isolated が指定されていると、Playwright は起動のたびに mkdtemp で完全にユニークな一時プロファイルを作成し、browserType.launch()(永続コンテキストではない起動方式)を使います。プロファイルの実体が毎回別物になるため、以前見られた mcp-chrome-<hash> のようなロック競合は発生しようがない、というのがソースコードから確認できた事実です。

一次情報の大切さ:サブエージェントの誤答を訂正できた

この調査では「一次情報にあたること」の価値を痛感しました。最初に調査を依頼した補助的な調査では「--isolated でもロック競合が起きる」という誤った回答が返ってきましたが、実際に playwright-core の該当コードを直接読むことで、それが誤りであると判明し訂正できました。ツールの挙動で迷ったら、ドキュメントや伝聞ではなく、インストール済みのソースを読むのが最短で確実です。

では今回の本当の原因は何だったのか?

ロック競合が原因でないなら、残る可能性は次の2つです。

  • 生きた参照の使い回し:同一セッション内で、既に開きっぱなしのブラウザ参照をそのまま使おうとして詰まるケース
  • 孤児プロセスの残留:以前のセッションがクラッシュなどで異常終了し、Playwright が起動した Chromium プロセスだけが孤児として残ってしまうケース

このうち後者、つまり「親が死んで孤児化した Chromium が居座っている」状態が、次のセッションでの起動を妨げる実害のある原因でした。ここをピンポイントで掃除すれば、次回は必ずクリーンな状態から始められます。

実施した対処:SessionStart フックで孤児プロセスだけ掃除する

フックの構成

対処は、Claude Code のユーザーグローバル設定にフックを1つ足すだけの軽量なものです。

  • ~/.claude/settings.jsonSessionStart フックを追加(ユーザーグローバル設定なので全プロジェクトに適用)
  • ~/.claude/hooks/cleanup-playwright-orphans.sh を新規作成し、新しいセッション開始のたびに自動実行

なぜ「孤児かつ Playwright 固有パス」だけを対象にするのか

スクリプトの判定は意図的に厳しく絞り込んでいます。「親プロセスが既に終了して孤児化しており(macOS では孤児は PID 1 に付け替えられる)、かつコマンドラインに Playwright MCP 固有の一時プロファイルパスを含む」プロセスだけkill します。

対象とするパスの目印は次の2つです。

  • playwright_chromiumdev_profile---isolated の一時プロファイル)
  • ms-playwright-mcp/mcp-(旧来の永続プロファイル)

この2条件(孤児であること+固有パスに一致すること)を同時に満たすものだけを落とすので、他ウィンドウで生きているブラウザや、普段使いの Chrome を巻き込む心配がありません。

この対処のメリットと限界は?

メリット

  • 安全性が高い:孤児かつ Playwright 固有パスに一致するものだけが対象。生きたブラウザや通常の Chrome を誤って落とすリスクが実質ない
  • 自動化:手動の pkill が不要になる
  • 既存動作への副作用なし:MCP サーバーの起動引数やブラウザ再利用ロジックは一切変更していないため、ログイン状態の維持など既存のワークフローを壊さない
  • 軽量psawk による一瞬のスキャンをセッション開始時に1回走らせるだけ

デメリット・限界

  • 「生きているブラウザが開きっぱなし」のケースには対処しない。これは意図された仕様(使い回し)なので、フックはあえて対象外にしている
  • 判定ロジックが macOS 依存(親の終了 → PID 1 への付け替え)。Linux / WSL では成立しない可能性があり、環境が変わったら見直しが必要
  • 新しいセッション開始時にしか走らない。今開いているセッション内で孤児が発生しても、次のセッション開始まで残る
  • パターンマッチ依存。将来 @playwright/mcp が一時プロファイルの命名規則を変えると、フックが静かに無効化される可能性がある(誤動作はしない)

検討したが採用しなかった代替案

安全性と運用コストの観点から、次の案はいずれも見送りました。

却下した理由
pkill -f "playwright|chromium" のような広範囲 kill 複数 VS Code ウィンドウを同時に使っているとき、他ウィンドウの生きたブラウザや無関係な Chrome まで巻き込むリスクが高すぎる
--user-data-dir を毎回ランダムな一時パスに固定 --isolated と併用できない。すでに --isolated で同じ目的を達成しており重複
--isolated を外し、永続プロファイル+状態ファイルで明示管理 運用が複雑化する割に、今回の問題解決にはつながらない

ログイン状態はなぜ保持されているように見えたのか?

--isolated の設計との矛盾

ここで一つ、重要な注意点があります。手元の運用メモには「未ログインの場合は一度ログインすれば、以降のセッションでもログイン状態が保持される」と書いてありましたが、これは --isolated 本来の設計(プロファイルを一時領域にのみ保持し、ディスクに永続化しない)と矛盾します。

本来、--isolated ではブラウザプロセスが一度完全に終了すれば、Cookie などのログイン情報も一緒に消えるはずだからです。

実際はブラウザプロセスが生き続けていただけ

種明かしをすると、「セッションをまたいでログインが保持されている」ように見えていたのは、実際には同じ VS Code ウィンドウでブラウザプロセスが長期間生き続けていた(一度も完全終了していなかった)ためと考えられます。永続化されていたのではなく、単に終了していなかっただけ、というわけです。

このため、次のようなトレードオフが生じます。

  • 今回のフックは孤児プロセスのみが対象なので、生きているセッションのログイン状態には影響しない
  • ただし「毎回確実に新しいまっさらなウィンドウを」という目的で、タスクごとに browser_close を呼ぶ運用に変えると、そのたびにログイン状態が失われ、再ログインの手間が増える可能性が高い

「常にクリーンな状態」と「ログインの手間」は両立しにくい、というのが正直なところです。

実務での使い分けTips

最後に、日々の運用で役立つ小ネタをまとめます。

  • 今すぐ孤児プロセスを確認・掃除したいとき~/.claude/hooks/cleanup-playwright-orphans.sh は単体でいつでも実行できる。次のセッションを待つ必要はない
  • 同一セッション内で確実にまっさらなウィンドウを開かせたいとき:次のタスクの前に browser_close ツールを呼ぶのが最も確実(ただしログイン状態は失われる)
  • 今回の設定変更は ~/.claude/settings.json(ユーザーグローバル)への追加なので、使っている全プロジェクトの Claude Code セッションに適用される
  • フックの効果が出るのは「次に新しく開始するセッション」から。今すでに開いているウィンドウでは、いったん終了して開き直すまで反映されない

まとめ

Playwright MCP の「前のブラウザが残って進まない」問題は、プロファイルロック競合ではなく、クラッシュ等で孤児化した Chromium プロセスの残留が実害の原因でした。そして「1ウィンドウ=1ブラウザ」はログイン状態を維持するための意図された仕様であり、複数ウィンドウでの並行利用は安全です。

対処としては、SessionStart フックで「孤児かつ Playwright 固有パスに一致するプロセスだけ」を掃除する、副作用の小さい方法を採りました。ツールの挙動に迷ったら、伝聞ではなくインストール済みのソースコードを読む——今回いちばんの教訓はそこにあります。

※記載内容は @playwright/mcp v0.0.78 時点での調査に基づきます。バージョンアップで内部実装や命名規則が変わる可能性があります。

ABOUT ME
りん
このブログでは、Web開発やプログラミングに関する情報を中心に、私が日々感じたことや学んだことをシェアしています。技術と生活の両方を楽しめるブログを目指して、日常で触れた出来事や本、グルメの話題も取り入れています。気軽に覗いて、少しでも役立つ情報や楽しいひとときを見つけてもらえたら嬉しいです。