Browser terminal attach to direct (non-orchestrator) Open Terminal fails 4004: OWUI WS proxy omits X-User-Id header

该报错发生在 Open WebUI 通过浏览器对直连(非 orchestrator)Open Terminal 进行 WebSocket 交互式终端连接时。原因是 Open WebUI 的 WS 代理在转发上游请求时未携带 X-User-Id 头,导致 open-terminal 的身份校验返回 4

快速结论:该报错发生在 Open WebUI 通过浏览器对直连(非 orchestrator)Open Terminal 进行 WebSocket 交互式终端连接时。原因是 Open WebUI 的 WS 代理在转发上游请求时未携带 X-User-Id 头,导致 open-terminal 的身份校验返回 4004 “Session not found”。优先排查 Open WebUI 与 Open Terminal 之间的 WS 代理是否正确转发用户身份头。

适用环境:Open WebUI v0.11.3,Open Terminal v0.12.4(ghcr.io/open-webui/open-terminal),后端代理模式且设置 OPEN_TERMINAL_MULTI_USER=true。Kubernetes + Cloudflare tunnel + Envoy Gateway 环境下复现,LAN 直连路径同样可复现,可排除网络层因素。

最快修复方案:暂无经过完整验证的一步修复方案。Issue 报告在确认根因为 Open WebUI 上游 WS 代理缺口后主动关闭,未给出经过验证的补丁。可优先尝试在 Open WebUI 的 routers/terminals.pyws_terminal 函数中,为上游 WS 连接补充与 REST 代理一致的 X-User-Id 头(需自行修改代码或等待官方修复)。

注意事项:上述代码修改属于建议方案,Issue 中未验证实际效果;修改涉及 Open WebUI 源码,升级版本后可能被覆盖。另外,open-terminal 侧存在次要缺陷:4004 关闭路径不会清理 PTY 会话,连续失败可能导致会话堆积直至达到 16 个上限后返回 429。

问题场景

用户在 Open WebUI 中使用浏览器内置终端,连接到一个直连(非 orchestrator)的 Open Terminal 服务器。所有 WebSocket 连接尝试均被上游以 4004 “Session not found” 关闭;Agent/REST 路径正常工作,只有浏览器的交互式 WS 连接失败,因为只有该路径经过 Open WebUI routers/terminals.py 中的 WS 代理。

每次失败的连接都会在 Open Terminal 服务器上遗留一个存活的 shell + PTY 会话,直到达到会话上限(16 个),此后新会话会收到 429 错误。

报错原文

Browser terminal attach to direct (non-orchestrator) Open Terminal fails 4004: OWUI WS proxy omits X-User-Id header
4004 "Session not found"

原因分析

Open WebUI 与 open-terminal 之间的两条代码路径对用户身份的传递方式不一致:

  • REST 代理路径(正常):Open WebUI 的 proxy_terminal 在上游请求中写入 X-User-Id 头。open-terminal 的 POST /api/terminals 将其存储为 session["user_id"],Agent 路径正常工作。
  • WS 代理路径(异常):Open WebUI 的 ws_terminaluser_id 放在上游 URL 的查询参数中(?user_id=<uuid>),且 upstream_headers = {}(除可选的 X-Terminal-Context-Id 外),从不转发 X-User-IdX-Session-Id

open-terminal 的 WS 所有权检查(open_terminal/main.pyws_terminal)仅比较请求头中的用户身份:

if (
    session["user_id"] != ws.headers.get("x-user-id", "")
    or session["chat_id"] != ws.headers.get("x-session-id", payload.get("chat_id", ""))
):
    await ws.close(code=4004, reason="Session not found")

因此 session["user_id"](uuid)与空的 header 不匹配,导致每次浏览器经过 OWUI 代理的连接都返回 4004。直接设置 header 的连接则可以正常工作。

环境排查

  • 确认 Open WebUI 版本为 v0.11.3,Open Terminal 镜像版本为 v0.12.4ghcr.io/open-webui/open-terminal)。
  • 确认 Open Terminal 为后端代理模式部署,且已设置 OPEN_TERMINAL_MULTI_USER=true
  • 在 Open WebUI Pod 内部直接复现(排除网络代理干扰),确认 LAN 路径同样复现,排除网关/隧道层问题。
  • 可通过真实流量抓包确认:HTTP→WS 升级在 Cloudflare tunnel 和 LAN 路径均返回 101,非网关问题。

解决步骤

  1. 定位问题(可优先尝试):确认为 Open WebUI 的 WS 代理代码缺口,非网络或配置问题。可对比相同连接带与不带 X-User-Id 头的表现:带 header 可正常连接并流式输出 PTY,不带则返回 4004。
  2. 修改 Open WebUI 源码(建议方案,未经验证):编辑 routers/terminals.py 中的 ws_terminal 函数,为上游 WS 连接补充与 REST 代理一致的身份头:
upstream_headers = {
    "X-User-Id": user.id,
}
# 当浏览器请求携带 X-Session-Id 时同时转发
  1. 注意安全性user 已在 _resolve_authenticated_connection 中通过验证的 JWT 服务端解析,非信任客户端提供的值,与 REST 代理行为一致。
  2. 防御性应对(可选,open-terminal 侧):在 open-terminal 侧,当 header 缺失时回退到 ?user_id= 查询参数,可增强直接 WS 客户端的鲁棒性。该改动可单独提交到 open-terminal 仓库。

验证方法

修改后重新启动 Open WebUI,通过浏览器发起交互式终端连接,确认能够正常建立 WebSocket 会话并看到 PTY 输出流。同时确认 open-terminal 日志中不再出现 4004 关闭记录,且无遗留的 shell 进程堆积。

参考来源

open-webui/open-webui #29768

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 22432

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注