人工智慧前沿

2026 年 macOS 上 OpenClaw 閘道埠號綁定:以 lsof 與 LaunchAgent 對齊排查 EADDRINUSE,並讓健康探針與真實存取路徑一致

MacHTML Lab2026.05.11約 30 分鐘閱讀

2026 年,OpenClaw 閘道上線最常被卡住的錯誤之一仍是 Error: listen EADDRINUSE: address already in use。團隊往往誤以為是上游模型 API 故障,反覆輪換金鑰,卻忽略昨天冒煙測試遺留的 node 行程仍占著 TCP 8787。macOS 上 launchd 可能在舊通訊端尚未完全釋放前就拉起新作業;多人共用一台租用的 Mac mini 時,設定檔與手動終端機工作階段疊加;合成健康檢查常常只存取 127.0.0.1,而閘道實際綁在內網位址。本文給出可複用的排查順序——先用 lsof,再核對 plist,再對齊探針——並與 doctor 閘道診斷首次執行與 LaunchAgent 冒煙閘道健康與可用性監控 串聯,減少拍腦袋式排障。

把埠號衝突當作容量類事故來記錄:寫明監聽介面、佔用 PID、LaunchAgent 標籤,事後覆盤才能一頁紙結束。

看起來像上游故障的症狀

用戶端可能看到連線被拒絕、curl 空回應,或面板顯示「閘道離線」,而 CPU 幾乎閒置。在指責模型廠商之前,先確認 HTTP 服務是否進入 LISTEN。若行程在啟動階段當機,日誌可能在列印橫幅前就截斷,只剩 errno。請收集 StandardErrorPath 的 stderr,並把時間戳與 launchctl print 的狀態遷移對齊。

多人共享租用的 Mac mini 時,遺留的 npm run dev 工作階段最常見;它們往往未接入生產 Grafana,因為從未註冊指標端點。

一分鐘內給出答案的 lsof 用法

lsof -nP -iTCP:8787 -sTCP:LISTEN

-n 跳過 DNS 解析以免拖慢掃描;-P 顯示數字埠號。若輸出裡出現不認識的 node PID,在殺行程前執行 ps -p PID -o args= 留存命令列——維運最討厭「無名行程」。若 lsof 無監聽而用戶端仍失敗,檢查是否用戶端走 HTTPS 而閘道該埠號只提供明文 HTTP。

對僅 IPv6 監聽,可再跑 lsof -nP -i6TCP:8787;部分 Node 堆疊會建立雙堆疊通訊端,列表裡可能出現兩次。

LaunchAgent ProgramArguments 與埠號參數

plist 裡常見同一 --port 被傳兩次:一次來自包裝指令碼,一次來自複製到各環境的範本,後者靜默覆寫前者,導致 diff 極難讀。保持單一事實來源——要么用 EnvironmentVariables,要么只用顯式參數,不要混用。編輯後執行 launchctl bootout gui/$UID/labellaunchctl bootstrap gui/$UID path,確保舊通訊端在啟新作業前關閉。

核對 WorkingDirectory 是否指向真正含有你以為在執行的那份 package.json 的目錄;cwd 錯配再加 npx 時,很容易在預設埠號上再起第二個監聽。

127.0.0.1、0.0.0.0 與區域網路 IP

綁定 127.0.0.1 能避免隨意的內網掃描,但會破壞從另一台主機發起的健康檢查——甚至同一台宿主機上另一顆虛擬機的探針。綁定 0.0.0.0 接受所有介面,必須搭配防火牆策略。綁定固定辦公室 IP 會在 DHCP 續租後悄悄失效。把約定寫進 runbook,並在 openclaw doctor 的預期裡同步同一套介面語意。

會「說謊」的健康探針

若合成監控只 curl http://127.0.0.1:8787/readyz,而遠端使用者存取 10.0.40.12:8787,在路由表或分流 VPN 不包含該子網時,探針會一直綠。讓探針來源 IP 與使用者真實路徑一致,或經同一堡壘機隧道。閘道在啟動時匯出 gateway_bind_interface 指標,便於 Grafana 發現各環境漂移。

決策表

情境首選綁定注意點
單人筆電實驗127.0.0.1遠端演示前記得切換
企業防火牆後的共用 Mac mini區域網路 IP + 白名單與 DHCP 預留或靜態租約協同
公網邊緣經反向代理回圈 + 前置 nginx盡量不要讓閘道行程直接暴露在公網介面

TIME_WAIT 與高頻重載

偵錯期每 30 秒 自動重啟的指令碼會讓舊連線處於 TIME_WAIT 長達約 60 秒,或耗盡暫時埠號。在 bootoutbootstrap 之間插入約 5 秒 等待,或在驗證熱修分支時把管理埠號暫時 +1

應用防火牆的坑

macOS 可能為每個新 Node 二進位路徑彈出「是否允許傳入連線」。若點了拒絕,本機綁定成功而遠端 SYN 逾時,表象像埠號衝突。統一二進位路徑或簽章流程,避免升級時對話框轟炸值班同事。

同一台 Mac 上多閘道

藍綠演練需要不同埠號(如 87878788)以及互不重複的 LaunchAgent 標籤。用表格約定埠號段:例如 8700–8799 預留給 OpenClaw,8800–8899 給模擬上游。沒有文件時,週末外包往往會隨便挑「看起來閒置」的埠號。

在同一實體機上並行跑 staging 與 production——租用 Mac mini 時很常見——至少分使用者或分日誌目錄,否則 lsof 輸出難以解讀。

launchctl kickstart 與殘留監聽

釋放埠號後優先使用 launchctl kickstart -k gui/$UID/com.example.openclaw,讓 launchd 對頑固子行程送出 SIGKILL,而不是禮貌等待。沒有 -k 時,掛起的中介軟體執行緒可能仍持有 FD,儘管父行程已列印「shutdown complete」。在前後各截取一頁 launchctl print gui/$UID/com.example.openclaw,證明狀態從 running 變為 not running

暫時用戶端埠號與出站風暴

工具呼叫扇出會開啟大量出站連線;macOS 可能耗盡暫時埠號區間,而入站 LISTEN 仍顯示正常。若值班把用戶端側的 EADDRINUSE 誤判為閘道監聽衝突,會白查一輪。調校與閘道同機的 CI runner 時,關注 sysctl net.inet.ip.portrange.hifirst 等參數。

綁定失敗的結構化日誌

輸出 JSON 日誌欄位 event="bind_failed"、errno、嘗試綁定的 host、port、以及 argv 雜湊。事後回放日誌時不應再靠 SSH 猜基礎事實。errno 48EADDRINUSE)應與同一條日誌裡的 lsof 快照命令並列,方便新人直接複製貼上。

為何 Linux CI 抓不到 macOS 綁定競態

容器重啟快、網路命名空間與 GUI 工作階段下的 macOS 行為不同。把 Linux CI 當作編譯期檢查;涉及 plist 的變更仍應在 Apple 硬體上做冒煙綁定。按天租用 Mac mini 的成本大約等於工程師一小時的時薪,卻能閉環驗證。

常見問題

macOS 會立刻複用同一埠號嗎?

高頻重啟後可能短暫處於 TIME_WAIT,需要等待或換暫時埠號。

為什麼健康檢查綠燈而使用者連不上?

探針與真實使用者的網路介面或路徑不一致。

0.0.0.0 比回圈更安全嗎?

暴露面更大,不是更安全;需配防火牆或隧道。

何時租用 Mac mini?

當你必須在筆電之外重現與生產一致的 macOS 通訊端與 launchd 生命週期時。

埠號爭奪無聊卻昂貴:每一次誤報都會消耗跨團隊信任。在 MacHTML 租用一台 Apple Silicon Mac mini,每天約 16.9 美元,即可獲得與生產閘道相同的 launchd 生命週期、預設通訊端行為與防火牆彈窗,而無需給每位外包寄硬體。發布週拉起實例收集 lsof 證據,佇列清空後再關機即可。

低發熱雜訊也有利於你長時間 SSH 反覆驗證綁定而不打擾鄰座。

在真實 macOS 上重現 OpenClaw 綁定問題

租用雲端 Mac mini,驗證埠號、LaunchAgent plist 與健康探針,獲得與 macOS 一致的通訊端行為。

雲端 Mac 驗證閘道埠號
低至 $16.9/天