Skip to content

Docker/Web 反代环境下自动 reload Gateway 会导致 WebSocket 重连循环 #308

Description

@tianxia3111

环境

  • ClawPanel: 0.16.5
  • OpenClaw Gateway: 2026.5.27
  • 部署方式: Docker/headless web server
  • 容器系统: Ubuntu 24.04
  • ClawPanel 监听: 0.0.0.0:1420
  • OpenClaw Gateway 监听: 127.0.0.1:18789
  • 外部访问: HTTPS + 反向代理,域名形如 https://example.com
  • WebSocket 地址: wss://example.com/ws?token=...

现象

页面刷新后,Dashboard/聊天页显示“WebSocket 握手中 / WebSocket 重连中 #0”。浏览器调试日志显示 WebSocket 实际已经连通并完成握手:

[ws:open] wss://example.com/ws?token=...
[ws:msg] {"type":"event","event":"connect.challenge",...}
[api] 200 /__api/create_connect_frame
[ws:msg] {"type":"res","ok":true,"payload":{"type":"hello-ok","protocol":4,"server":{"version":"2026.5.27"}}}
[ws:msg] {"type":"event","event":"health","payload":{"ok":true,...}}

随后前端会请求:

[api] 500 /__api/reload_gateway

容器日志中能看到 Gateway 被 SIGTERM,导致刚建立的 WebSocket 被关闭,然后页面进入重连循环:

[gateway] signal SIGTERM received
[gateway] received SIGTERM; shutting down
[shutdown] websocket server close exceeded 1000ms; forcing shutdown continuation with 1 tracked client(s)
exited: openclaw (exit status 0; expected)
spawned: 'openclaw' with pid ...

根因分析

这不是单纯“反代没有开启 WebSocket”的问题。因为浏览器已经收到了 connect.challengehello-okhealth,说明 /ws 已经通过反代连到了 Gateway。

现场排查发现主要有几条自动重载路径在 Docker/Web/headless + 反代环境下会互相打架:

  1. src/main.js -> autoConnectWebSocket() 在自动连接前会执行 api.autoPairDevice()api.patchModelVision(),只要认为配置有变化,就调用 api.reloadGateway()。在 Web/Docker 模式中,这会把刚准备建立或刚建立的 Gateway WS 会话重启掉。

  2. src/lib/ws-client.js -> _autoPairAndReconnect() 在自动配对后也会直接调用 api.reloadGateway(),同样会造成 Gateway 重启和 WS 断开。

  3. src/lib/tauri-api.js -> _debouncedReloadGateway() 会在 writeOpenclawConfig() / calibrateOpenclawConfig() 后 3 秒自动调用 reload_gateway。这在桌面端也许合理,但在 Web/Docker 模式中容易变成“后台配置写入 -> 自动重载 Gateway -> WS 被踢掉”的循环。

另外还有一个 UI 层问题:src/pages/dashboard.js -> renderWsStatus() 会根据 wsClient.reconnectState 渲染“WebSocket 重连中 #0”。Dashboard 初次渲染时如果刚好处于 attempting/scheduled,后续 wsClient 进入 ready 后该状态块没有及时订阅 ready/status 事件刷新,导致页面看起来仍然是“重连中 #0”,即使实际 WS 已经 hello-ok

临时验证/本地补丁

在现场容器里做了以下临时补丁后恢复正常:

  1. Web/Docker 模式下,禁止自动连接路径调用 reloadGateway
  2. Web/Docker 模式下,禁止 _autoPairAndReconnect() 自动 reload Gateway。
  3. Web/Docker 模式下,_debouncedReloadGateway() 直接 return,只保留显式按钮/服务页的手动 restart/reload。
  4. Headless server 里把 /__api/reload_gateway 暂时改成 no-op,避免旧前端缓存继续触发 Gateway 重启。
  5. Dashboard 增加 wsClient.onStatusChange / wsClient.onReady,只刷新 WebSocket 状态块。
  6. 静态 assets 响应头从 public, max-age=31536000, immutable 改为 no-cache, no-store, must-revalidate,避免 Docker 中临时修复后浏览器仍长期使用旧 JS。

补丁后浏览器显示:

WebSocket 已连接
Gateway 2026.5.27

Gateway 不再出现新的 SIGTERM。

建议修复

建议区分 Tauri 桌面端和 Web/headless Docker 模式:

  • 自动连接、自动配对、配置校准等“隐式流程”不要在 Web/headless 模式自动重启 Gateway。
  • 如果确实检测到配置变化,可以提示用户“需要手动重载 Gateway”,或只在明确点击保存/重载按钮时执行。
  • writeOpenclawConfig(config, opts) 建议默认不隐式 reload,或至少在 Web/headless 下默认 noReload: true
  • reload_gateway API 在 headless 模式中最好有更明确的调用来源和日志,便于排查是谁触发重启。
  • Dashboard 的 WebSocket 状态块应订阅 wsClient.onStatusChange / onReady,避免停留在首次渲染时的 reconnecting #0
  • Docker/headless 版如果需要热修或升级,assets 使用 immutable 长缓存时要确保 HTML hash 变化能可靠让用户拉到新 bundle。

预期行为

在 Docker/Web + HTTPS 反代环境中,只要 /ws 已成功完成 hello-ok,ClawPanel 不应自动重启 Gateway 导致 WS 断开;Dashboard 应及时显示“WebSocket 已连接 / Gateway 版本”。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions