Repository navigation
fix(web-search): 连接层瞬时失败重试,并让 fetch failed 带原因与目标(#117) - #118
Conversation
报告者在 0.16.3 上 10 分钟内 14 次 web_search 失败 3 次,4-query 的 2 次调用 全部失败,单 query 12 次失败 1 次;失败发生在拿到 HTTP 状态之前,面板记录 21.738 s、0 token、$0.0000,同一 query 立即重试全部成功。 - undici 的 fetch 把连接/读取阶段失败统一包成 TypeError: fetch failed,真实 原因码在 error.cause;本机对不可达地址实测 10.5 s 后为 UND_ERR_CONNECT_TIMEOUT,双地址族各等一轮可到约 20 s,与报告时长同量级。 - 搜索此前只发一次 fetch、失败即抛;DSH 的 dsh-tool-web 又在多 query 时第一个 失败就 abort 兄弟并整体抛出,于是一次抖动丢掉整批结果。 - 现在仅对「未收到 HTTP 响应」重试,最多 3 次尝试(2 次重试),退避 500 ms→1 s 且 ±20% 抖动;账号只在进入重试前解析一次;等待监听调用方信号,取消或工具 协程超时仍为 WEB_ABORTED。已收到 HTTP 状态(含 401/403/429)与响应体解析 失败不重试——那些是结论而非抖动。 - 最终错误追加 cause 原因码、目标 origin(httpOrigin 丢弃路径与查询)、耗时与 尝试次数,复用 transportFailureReason(),`fetch failed` 不再同时承担 DNS、 代理、超时三种解释。 - 多 query 的部分成功仍属 DSH 的 dsh-tool-web 策略,未在本插件改动。 验证:typecheck 退出码 0;npm test 1161/1161(新增 5 条:重试成功且密钥只 解析一次、3 次后停止并在错误串带原因/目标/次数、403 不重试、退避等待可被 取消打断、退避基数与抖动边界);build 两段 Build complete。另用真实 HTTP server 复验:首个请求断连后第 2 次请求成功(约 444 ms);一直断连时 3 次请求 后错误串为「连接中断(UND_ERR_SOCKET);目标 http://127.0.0.1:…;耗时 1693ms;已尝试 3 次」;100 ms 时取消则只发 1 次请求、102 ms 返回 WEB_ABORTED。 Co-authored-by: Mars-Sea <33591741+Mars-Sea@users.noreply.github.com>
独立审查评论(不阻塞合并)结论:没有阻塞问题,可以合并。CI 全绿;我在隔离副本里独立复跑了测试与构建,并用真实 HTTP server(不是 fetch stub)验证了三条路径。以下按优先级列出建议,其中 P2 主要是文档/取舍问题,P3 都是可选优化。 已独立核验
P2:重试预算与
|
|
收到审查意见:会补上重试预算与取消的持久文档、修正“响应体失败”措辞、把取消测试改成确定性,并保持重试判定范围不变、注明理由。 |
- accounts-and-retry / 决策记录:补充 3 次连接超时尝试加两次退避约 31.5–33 s, 超过 dsh-tool-web 默认 30 s 预算;慢失败时 deadline 落在重试期间,最终是 WEB_ABORTED、诊断串不会出现(报告者 21.7 s 首败只剩约 8 s)。说明未加 per-attempt 超时或按 elapsed 收敛的理由。 - 修正「响应体失败不重试」的理由:响应头已到、请求已被接受并可能执行,重放有 重复计费代价,而不是「响应体失败是答案」。 - 注明所有非取消的 fetch 拒绝都按瞬时处理,以及最多约 1.5 s 的有界代价;不引入 可能遗漏未收录瞬时码的码表 allowlist。 - 测试:导出 searchRetryWait,取消用例注入生产等待并等其就绪,去掉真实 500 ms 定时器与耗时断言;新增传给等待函数的 500 ms→1 s 延迟序列用例。 - 重建 lib/。 验证:typecheck 退出码 0;npm test 1162/1162(tests/web-search.test.ts 34/34); build 两段 Build complete;git diff --check 干净。 Co-authored-by: Mars-Sea <33591741+Mars-Sea@users.noreply.github.com>
dd94e7f to
45fb1b5
Compare
复核结果:上一轮问题已解决;剩余几处瑕疵请处理在 head
请修改
建议(可选)
另:force-push 重写了历史(原 |
- `retryWait` 注释不再 `{@link}` 未导出到 lib/index.d.ts 的 searchRetryWait;
模块注释中的 SEARCH_MAX_ATTEMPTS 与 transportFailureMessage 注释中的
httpOrigin 改为纯文本,避免产物里的链接无法解析。
- searchRetryWait 说明改为「without waiting out a real 500 ms timer」:测试仍会
创建生产定时器,只是取消后立即 clear、不等待它触发。
- 新增不注入 retryWait 的取消用例(fake timers + 捕获的真实 guard 定时器),
覆盖 `retryWait ?? searchRetryWait` 默认接线;已用变异验证(移除 abort 监听
时该用例失败)。
- 按文档精炼口径收敛重复算术:完整预算边界保留在
docs/agent-reference/accounts-and-retry.md 与 docs/决策记录.md,CHANGELOG
和源码注释压成结论 + 指针。
- 重建 lib/。
验证:typecheck 退出码 0;npm test 1163/1163(tests/web-search.test.ts 35/35);
build 两段 Build complete;git diff --check 干净。
Co-authored-by: Mars-Sea <33591741+Mars-Sea@users.noreply.github.com>
This is by an automated agent, not a human.
调查结论
issue #117 的
fetch failed发生在拿到 HTTP 状态之前,因此不是 401/403/429,也不是套餐或凭据结论。它由两个独立事实叠加:fetch把连接/读取阶段失败统一包成裸的TypeError: fetch failed,真实原因码在error.cause。本机对不可达地址实测 10.5 s 后为UND_ERR_CONNECT_TIMEOUT,双地址族各等一轮可到约 20 s,与报告者面板的 21.738 s 同量级。fetch、失败即抛;DSH 的dsh-tool-web在多 query 时第一个失败就 abort 同批其余请求并整体抛出。一次连接抖动因此丢掉整批 query 的结果。不涉及本仓库的两个可能因素也已排除:默认
apiBase无反代前缀(#111 不适用),报告环境无代理变量(#59 不适用)。改动
CommandCodeSearchProvider.search()只对「未收到 HTTP 响应」的连接层失败重试:最多 3 次尝试(首发 + 2 次),退避 500 ms → 1 s、±20% 抖动;账号在进入重试前只解析一次。重试等待监听调用方信号:用户取消或工具协程超时仍以
WEB_ABORTED结束,不会睡满退避。已收到 HTTP 状态(含 401/403/429)不重试——那是服务端的答案;响应体失败也不重试,因为响应头已到、请求已被接受并可能执行,重放有重复计费的代价。所有非取消的
fetch拒绝都按瞬时处理(含证书、参数类确定性错误),代价是最多约 1.5 s 后抛出同一错误。最终错误追加
error.cause原因码(复用transportFailureReason())、目标 origin(httpOrigin()丢弃路径与查询,不回显反代前缀或凭据)、耗时与尝试次数。示例:同步
CHANGELOG.md、docs/agent-reference/accounts-and-retry.md与docs/决策记录.md第十一章第 3 条,并重建lib/。未改动 / 边界
dsh-tool-web,不在本插件;要返回部分结果需上游修改。dsh-tool-web默认 30 s 的searchTimeoutMs,工具 deadline 落在重试期间则按取消(WEB_ABORTED)上报,最后一次连接层原因不会出现在错误串里。完整算术与取舍见docs/agent-reference/accounts-and-retry.md;未加每次尝试超时(新的用户可见契约),也未按已耗时收敛(会跳过本 issue 的慢失败场景)。验证
npm run typecheck退出码 0。npm test1163/1163(tests/web-search.test.ts35/35);新增 7 条:第二次尝试成功且密钥只解析一次、3 次后停止并带原因/目标/次数、403 不重试、注入生产等待函数的取消用例、默认retryWait ?? searchRetryWait接线的取消用例(fake timers + 真实 guard 定时器)、退避基数与抖动边界、传给等待函数的 500 ms→1 s 延迟序列。npm run build两段Build complete,git diff --exit-code -- lib/干净。WEB_ABORTED。test:engine、test:install、test:pack。复现边界
本机未能复现报告者的 Windows + 6 子代理并发条件;上述诊断基于其 issue 证据、undici 源码行为与本机网络实测。重试用例和真实断连复验均在本机完成,未使用真实 Command Code 账号做并发搜索。