AI Frontier

2026年・macOS 上の OpenClaw ゲートウェイ:EADDRINUSE を lsof と LaunchAgent で切り分け、ヘルスプローブを実クライアントに揃える

MacHTML Lab2026.05.11約30分

2026 年も OpenClaw のロールアウトを止める代表例は Error: listen EADDRINUSE: address already in use だ。チームは「ポートが埋まっている」と「モデル API が落ちている」を混同し、キーを何度も回しても、昨日のスモークで残った nodeTCP 8787 を握っていることに気づかない。macOS では launchd が旧ソケットが完全に閉じる前に新ジョブを立てたり、レンタルの Mac mini を複数プロファイルで共有したり、合成ヘルスが 127.0.0.1 だけを叩き、ゲートウェイは LAN に bind していたりする。本稿では lsof → plist の一致 → プローブ整合という順序を固定し、doctor ゲートウェイ診断初回 LaunchAgent スモークヘルス監視 とつなげて推測を減らす。

ポート衝突はキャパシティ事故として記録し、インターフェース・PID・LaunchAgent ラベルを残すとポストモーテムが短く済む。

ポート帯の設計なしに blue/green すると、週末に同じ番号へ二重起動しがちだ。表で帯域を予約しておく。

上流障害に見える症状

接続拒否、空の curl、CPU が遊んでいるのに「オフライン」表示など、上流を疑う前に LISTEN に至ったか確認する。起動直後に落ちるとバナーより先に stderr が途切れることもある。StandardErrorPathlaunchctl print の状態遷移を突き合わせる。

共有 Mac mini では放置された npm run dev が典型で、メトリクス未登録のため Grafana に出ない。

DNS や TLS の問題も似た症状を作る。順序はソケット → TLS → アップストリーム。

1分で答えが出る lsof

lsof -nP -iTCP:8787 -sTCP:LISTEN

-n で DNS 遅延を避け、-P で数値ポート。未知の node PID は kill 前に ps -p PID -o args=。誰も listen していないのに失敗する場合は HTTPS クライアントと HTTP サーバの不一致を疑う。

IPv6 のみなら lsof -nP -i6TCP:8787。デュアルスタックでは二重表示があり得る。

LaunchAgent ProgramArguments

ラッパーとテンプレの二重 --port は後勝ちで静かに上書きする。真実は一つ:EnvironmentVariables か明示引数のどちらか。編集後は bootoutbootstrap で旧 FD を閉じてから起動する。

WorkingDirectory が想定の package.json を含むか確認。誤った cwd と npx はデフォルトポートの二重 listen を招く。

127.0.0.1 / 0.0.0.0 / LAN

ループバックは簡易スキャンを避けるが他ホストからのヘルスを壊す。0.0.0.0 はファイアウォール必須。固定オフィス IP は DHCP 更新で壊れる。Runbook と openclaw doctor の前提を揃える。

嘘をつくヘルス

curl http://127.0.0.1:8787/readyz だけが緑でも、ユーザーは 10.0.40.12:8787 で split VPN に阻まれる。プローブの送信元を実経路に合わせるか、同じ踏み台でトンネルする。起動時に gateway_bind_interface をメトリクス化して Grafana で環境差を見える化する。

L4 のヘルスが緑でも L7 認証が落ちることはある。ソケット確認に最小の認証付き HTTP を足す。

意思決定表

シナリオ推奨 bind注意
単独ラボ127.0.0.1リモートデモ前に切替
共有 Mac mini(社内 FW 内)LAN IP + allowlistDHCP 予約と整合
公開エッジ+リバプロloopback + nginxゲートウェイを公開側インターフェースに直接出さない

TIME_WAIT と高頻度リロード

30秒ごとの再起動は TIME_WAIT を最大約60秒残したりエフェメラルを枯らす。bootoutbootstrap の間に約5秒、または検証中は管理ポート +1。

アプリケーションファイアウォール

新しい node バイナリパスごとに着信許可ダイアログが出る。拒否するとローカル bind は成功するが遠隔 SYN がタイムアウトし、ポート競合に見える。パスと署名運用を標準化する。

1台に複数ゲートウェイ

Blue/green は 8787/8788 のようにポート分離と一意ラベルが必要。8700–8799 を OpenClaw、8800–8899 をモックに割り当て、表を共有しないと外注が適当に選ぶ。

同一ホストで staging と production を並走するならユーザーかログを分離し、lsof を読める状態に保つ。

launchctl kickstart

解放後は launchctl kickstart -k gui/$UID/com.example.openclaw を優先し、launchd に頑な子プロセスへの SIGKILL を任せる。-k なしだとミドルウェアスレッドが FD を握ったまま「shutdown complete」とログする場合がある。前後で launchctl print を保存し runningnot running を示す。

エフェメラルクライアントポート

ツール扇出は大量の外向き接続を開き、macOS はエフェメラル帯を枯らしても inbound LISTEN は正常に見える。クライアント側 EADDRINUSE をリスナー競合と取り違えない。同居 CI ランナーでは sysctl net.inet.ip.portrange.hifirst を観測する。

bind 失敗の構造化ログ

event="bind_failed"、errno、host、port、argv ハッシュを JSON で。errno 48 と同じ行に lsof コマンドを載せ、新人がコピペで追えるようにする。

Linux CI が macOS の競合を取りこぼす理由

コンテナのネットワーク名前空間は GUI セッションの launchd と異なる。Linux CI はコンパイル確認に留め、plist 変更は Apple 実機でスモーク bind を行う。1日分の Mac mini レンタル(約16.9 USD)はエンジニア1時間程度のコスト感でギャップを埋める。

FAQ

macOS はポートをすぐ再利用する?

高頻度再起動後は待つか一時ポート変更。

ヘルスは緑なのにユーザーは失敗?

プローブ経路とユーザー経路が違う。

0.0.0.0 はループバックより安全?

いいえ。露出が広い。FW が必要。

いつ Mac mini を借りる?

ノート外で本番に近い macOS ソケット挙動が要るとき。

ポート争いは地味だが高コスト。Apple Silicon Mac mini を MacHTML で日約16.9 USD借りれば、本番と同じ launchd ライフサイクルとファイアウォール挙動を再現できる。リリース週だけ立て、lsof 証跡を集めたら止めればよい。

静音性は長時間の bind 検証 SSH に効く。

実機 macOS で OpenClaw の bind を再現

クラウド Mac mini でポート・plist・ヘルスを、macOS 本来のソケット挙動で検証する。

ゲートウェイポート QA
約 $16.9/日〜