[Bug]: Node.js SDK retries workflow POST after a transport failure

当 Node.js SDK 的 WorkflowClient.run() 在遇到网络错误或超时后,SDK 的默认重试逻辑会再次发送 POST /workflows/run ,导致一次调用产生两次 fetch ,可能引发重复执行 workflow 的副作用。优先排查调用是否使用了 dify-clien

快速结论:当 Node.js SDK 的 WorkflowClient.run() 在遇到网络错误或超时后,SDK 的默认重试逻辑会再次发送 POST /workflows/run,导致一次调用产生两次 fetch,可能引发重复执行 workflow 的副作用。优先排查调用是否使用了 dify-client 的默认重试配置或显式设置了正的 maxRetries。

适用环境:仓库来源 langgenius/dify main 分支 commit e6f1d77ed5869e659e426b5459439d44bdabdbe5;Node.js SDK 包 dify-client 3.1.0。Issue 未提供操作系统、Node.js 具体版本等其他环境证据。

最快修复方案:暂无确认的一步修复方案。Issue 讨论确认该重试安全缺口在 Node.js SDK 中尚未修复,评论也指出相关改动(PR #34325、PR #37313)均未覆盖此 SDK 场景。

注意事项:该 Issue 的验证仅限于 SDK 测试环境(Vitest mock)中确认了重复的请求发送,并未验证真实 server 上的重复执行。讨论明确说明 GET/PUT/DELETE 的重试以及 HTTP 429 的限流重试不在本缺陷范围内,相关改动不要误伤这些路径。

问题场景

用户在 Node.js 项目中使用 dify-client Node.js SDK(版本 3.1.0)调用 WorkflowClient.run() 执行阻塞式 workflow 时触发该问题。该调用最终通过 HttpClient.request() → requestRaw() 发出 POST /workflows/run 请求。当首个 POST 请求遇到网络错误或超时,SDK 会自动重试,使得调用方只调用一次 WorkflowClient.run(),但传输层却对同一请求体再次发起 fetch。

报错原文

[Bug]: Node.js SDK retries workflow POST after a transport failure

HttpClient.requestRaw() retries when preparedBody.replayable && shouldRetry(mapped, attempt, maxRetries) is true

shouldRetry() checks only the error type (TimeoutError, NetworkError, RateLimitError) and never the request method

expect(fetchMock).toHaveBeenCalledTimes(1)
// actual: 2 calls

原因分析

根本原因在于 SDK 的重试判断没有考虑 HTTP 方法的幂等性:

  • prepareRequestBody() 会把任意 JSON 请求体标记为 replayable: true,无论方法是什么。它只说明字节可以被再次序列化发送,并不能说明重新执行 workflow 是安全的。POST/PATCH/PUT/DELETE 带 JSON 体时都会像 GET 一样被重试。
  • shouldRetry() 只检查错误类型(TimeoutError、NetworkError、RateLimitError),从不检查请求方法。
  • HttpClient.requestRaw() 在 preparedBody.replayable && shouldRetry(...) 为真时执行重试;默认 maxRetries 为 3。只有可读流会通过 replayable: false 主动退出重试,普通 JSON 的 workflow 请求不受此保护。
  • 由于 WorkflowClient.run() 的 POST /workflows/run 之间没有方法感知的守卫,它会走和普通 JSON 写请求相同的重放路径。

可能原因:SDK 无法判断服务器在响应丢失前是否已经执行了 workflow,因此在非幂等的 POST/PATCH 上自动重发是不安全的。

环境排查

  • 确认使用的 dify-client Node.js SDK 版本是否为 3.1.0(或从 main commit e6f1d77ed5869e659e426b5459439d44bdabdbe5 构建)。
  • 确认代码中是否使用了 SDK 默认重试配置,或显式传入了正的 maxRetries。
  • 确认调用的入口是否为 WorkflowClient.run(),以及其 response_mode 是否为 blocking。
  • 确认请求路径是否为 POST /workflows/run。
  • Issue 未提供操作系统、Node.js 运行时具体版本、CUDA、显卡或 Python 相关信息,这些不要自行补写。

解决步骤

  1. 先按 Issue 的复现片段在测试中确认现象:在 sdks/nodejs-client/src/http/client.test.ts 中,把 fetch mock 为首次 reject(new Error('connection lost'))、第二次返回 200 JSON,用 maxRetries: 1、retryDelay: 0 调用 requestRaw({ method: 'POST', path: '/workflows/run', ... }),断言 expect(fetchMock).toHaveBeenCalledTimes(1)。在 e6f1d77... 上该断言会看到 2 次调用。
  2. 确认这是非幂等方法在传输失败后被自动重试导致的。讨论已确认代码行为与报告一致,无需先假设是其他配置问题。
  3. 如需要临时规避,将 workflow 调用入口改为不经过该自动重试路径(例如避免使用带默认/正 maxRetries 的 JSON POST 重放路径),但 Issue 中没有给出已验证的规避命令或配置项,此项可优先尝试,需自行验证。
  4. 跟踪上游修复:讨论指出 PR #34325(axios 到 fetch 的传输重写)只是原样搬运了重试逻辑,未加入方法感知;PR #37313(CLI difyctl 的 429/幂等处理)只作用于 CLI,不覆盖此 SDK。可关注是否有针对 Node.js SDK 的新 PR 把 GET/PUT/DELETE 与 POST/PATCH 的重试行为区分开。
  5. 在修复落地前,对 workflow 这类有副作用的写请求,避免依赖 SDK 的自动重试;由调用方自行实现幂等或去重判断。

验证方法

在本地测试中按上述复现片段运行后,expect(fetchMock).toHaveBeenCalledTimes(1) 应当通过(即传输失败后不再对非幂等 POST 自动重发)。同时确认 GET/PUT/DELETE 的瞬时错误重试以及 HTTP 429 的限流重试仍然保留。注意:Issue 只验证了测试环境中的重复请求发送,若要确认线上是否发生重复 workflow 执行,需要服务端日志或去重机制另行核实。

参考来源

langgenius/dify #43077

GamsGo AI

AI 工具推荐

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

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

了解 GamsGo AI

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

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 27609

发表回复

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