人工智能前沿

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 能避免 casual 内网扫描,但会破坏从另一台主机发起的健康检查——甚至同一宿主机上另一颗虚拟机的探针。绑定 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/天