问题描述
启用 OpenAI Responses WebSocket 后,如果客户端请求在上游 WebSocket 尚处于 CONNECTING
状态时取消,app 可能抛出以下未捕获异常并退出:
Error: WebSocket was closed before the connection was established
[Lifecycle] uncaughtException
在使用 restart: unless-stopped 的 Docker Compose 部署中,进程退出会被自动拉起,表现为:
- app 运行时长频繁重置。
- 正在进行的流式请求全部中断。
- 前端短时间不可访问。
- 容器没有 OOM,PostgreSQL 和 Redis 仍然健康。
一次实际运行中,同一容器在数小时内因此自动重启 11 次,所有 fatal 日志均为同一个异常。
复现条件
- 系统设置
enableOpenaiResponsesWebsocket=true。
- 客户端通过 WebSocket 请求
/v1/responses。
- 选中 Codex 类型 Provider。
- 上游 WebSocket 尚未完成握手时,客户端断开或 AbortSignal 被触发。
根因分析
src/app/v1/_lib/responses-ws/upstream-adapter.ts 中:
onAbort() 调用 finishRequest({ closeCode: 1000, forgetSession: true })。
finishRequest() 首先调用 cleanupRequestListeners(),移除 error 监听器。
- 随后通过
closeAndForget() 调用 ws.close(code)。
- 当 socket 仍为
CONNECTING 时,ws 会异步发出
WebSocket was closed before the connection was established 错误。
- 因为请求级
error 监听已经移除,错误逃逸到进程级 uncaughtException。
src/instrumentation.ts 的崩溃处理器执行 process.exit(1),Docker 随后重启 app。
这是请求级取消事件被放大为整个服务重启的问题。
期望行为
- 请求在 WebSocket 握手阶段取消时,只终止该请求和对应上游 socket。
- 不产生进程级
uncaughtException 或 unhandledRejection。
- app 不退出、不重启,其他请求不受影响。
- 禁用上游 Responses WebSocket 时,应继续通过 HTTP Responses 正常转发。
当前配置局限
当前开关仅存储在数据库字段:
system_settings.enable_openai_responses_websocket
代码中没有对应环境变量。系统设置表单虽然读取并保存
enableOpenaiResponsesWebsocket,但当前 JSX 没有渲染该 Switch,因此部分版本的管理页面无法关闭它。
直接修改数据库可以临时关闭功能,但不利于不可变镜像部署、灾难恢复和后续版本升级。
建议的环境变量接口
增加一个可选的三态覆盖变量:
# 未设置:继续读取数据库系统设置,保持现有行为。
# false:强制禁用上游 OpenAI Responses WebSocket。
# true:强制启用上游 OpenAI Responses WebSocket。
ENABLE_OPENAI_RESPONSES_WEBSOCKET=false
优先级建议:
有效环境变量覆盖值 > 数据库系统设置 > 当前默认值
必须采用“未设置时读取数据库”的三态语义,不能把默认环境变量值设为 false,否则会静默改变现有部署。
本地部署可以立即使用:
ENABLE_OPENAI_RESPONSES_WEBSOCKET=false
Compose 已通过 env_file: ./.env 注入变量,不需要数据库迁移,也不需要修改 Compose 的
environment 列表。
问题描述
启用 OpenAI Responses WebSocket 后,如果客户端请求在上游 WebSocket 尚处于
CONNECTING状态时取消,app 可能抛出以下未捕获异常并退出:
在使用
restart: unless-stopped的 Docker Compose 部署中,进程退出会被自动拉起,表现为:一次实际运行中,同一容器在数小时内因此自动重启 11 次,所有 fatal 日志均为同一个异常。
复现条件
enableOpenaiResponsesWebsocket=true。/v1/responses。根因分析
src/app/v1/_lib/responses-ws/upstream-adapter.ts中:onAbort()调用finishRequest({ closeCode: 1000, forgetSession: true })。finishRequest()首先调用cleanupRequestListeners(),移除error监听器。closeAndForget()调用ws.close(code)。CONNECTING时,ws会异步发出WebSocket was closed before the connection was established错误。error监听已经移除,错误逃逸到进程级uncaughtException。src/instrumentation.ts的崩溃处理器执行process.exit(1),Docker 随后重启 app。这是请求级取消事件被放大为整个服务重启的问题。
期望行为
uncaughtException或unhandledRejection。当前配置局限
当前开关仅存储在数据库字段:
代码中没有对应环境变量。系统设置表单虽然读取并保存
enableOpenaiResponsesWebsocket,但当前 JSX 没有渲染该 Switch,因此部分版本的管理页面无法关闭它。直接修改数据库可以临时关闭功能,但不利于不可变镜像部署、灾难恢复和后续版本升级。
建议的环境变量接口
增加一个可选的三态覆盖变量:
优先级建议:
必须采用“未设置时读取数据库”的三态语义,不能把默认环境变量值设为 false,否则会静默改变现有部署。
本地部署可以立即使用:
Compose 已通过
env_file: ./.env注入变量,不需要数据库迁移,也不需要修改 Compose 的environment列表。