快速结论:当 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-clientNode.js SDK 版本是否为3.1.0(或从maincommite6f1d77ed5869e659e426b5459439d44bdabdbe5构建)。 - 确认代码中是否使用了 SDK 默认重试配置,或显式传入了正的
maxRetries。 - 确认调用的入口是否为
WorkflowClient.run(),以及其response_mode是否为blocking。 - 确认请求路径是否为
POST /workflows/run。 - Issue 未提供操作系统、Node.js 运行时具体版本、CUDA、显卡或 Python 相关信息,这些不要自行补写。
解决步骤
- 先按 Issue 的复现片段在测试中确认现象:在
sdks/nodejs-client/src/http/client.test.ts中,把fetchmock 为首次reject(new Error('connection lost'))、第二次返回200JSON,用maxRetries: 1、retryDelay: 0调用requestRaw({ method: 'POST', path: '/workflows/run', ... }),断言expect(fetchMock).toHaveBeenCalledTimes(1)。在e6f1d77...上该断言会看到 2 次调用。 - 确认这是非幂等方法在传输失败后被自动重试导致的。讨论已确认代码行为与报告一致,无需先假设是其他配置问题。
- 如需要临时规避,将 workflow 调用入口改为不经过该自动重试路径(例如避免使用带默认/正
maxRetries的 JSONPOST重放路径),但 Issue 中没有给出已验证的规避命令或配置项,此项可优先尝试,需自行验证。 - 跟踪上游修复:讨论指出 PR #34325(axios 到 fetch 的传输重写)只是原样搬运了重试逻辑,未加入方法感知;PR #37313(CLI
difyctl的 429/幂等处理)只作用于 CLI,不覆盖此 SDK。可关注是否有针对 Node.js SDK 的新 PR 把 GET/PUT/DELETE 与 POST/PATCH 的重试行为区分开。 - 在修复落地前,对 workflow 这类有副作用的写请求,避免依赖 SDK 的自动重试;由调用方自行实现幂等或去重判断。
验证方法
在本地测试中按上述复现片段运行后,expect(fetchMock).toHaveBeenCalledTimes(1) 应当通过(即传输失败后不再对非幂等 POST 自动重发)。同时确认 GET/PUT/DELETE 的瞬时错误重试以及 HTTP 429 的限流重试仍然保留。注意:Issue 只验证了测试环境中的重复请求发送,若要确认线上是否发生重复 workflow 执行,需要服务端日志或去重机制另行核实。
参考来源
AI 工具推荐
想把多个 AI 模型放在一个入口?
GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。
推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。
这个方案解决了吗?
可以继续搜索完整报错,或查看同一工具的其他排查指南。


