Skip to content

fix(providers): 退避期间预算耗尽后仍会多发一次重试 #320

Closed

Description

@huyengiang101086-spec

问题

runWithRetry() 只在 await sleep(delay) 前检查注入的 RetryBudget.isExhausted()。预算在退避等待期间耗尽时,下一轮仍然先执行 provider operation,再检查预算;如果这次额外调用成功,还会返回 stoppedByBudget: false。

这违反了 RetryBudget 对同一 Provider 重试次数的限制。公开导出的 helper 和 ProviderRouter 的 retry.budget 接线均可复现。

确定性复现

基线:main a6da0059ecafe4426c9a841ec59215655ebcabd1。

注入假时钟和 sleep,预算截止点为 100ms,首轮失败后等待 200ms:

let now = 0
let calls = 0
const result = await runWithRetry(async () => {
  calls += 1
  return { ok: false, error: { code: 'TIMEOUT', message: 'timeout' } }
}, {
  maxAttempts: 3,
  baseDelayMs: 200,
  jitter: false,
  sleep: async (ms) => { now += ms },
  budget: { isExhausted: () => now >= 100 },
})

实际:调用 2 次;第二次发生在预算已耗尽后。

预期:调用 1 次,返回首轮错误及 attempts: 1、stoppedByBudget: true。

验证

Bun 1.4.2 / Windows 11 (10.0.26200)。

新回归先在未修复生产代码上执行:

bun test packages/shared/src/providers/resilience.test.ts
→ 25 pass / 4 fail

四项失败覆盖 TIMEOUT、RATE_LIMITED、第二次退避耗尽预算,以及 ProviderRouter 的实际同源重试路径;不访问外部 API。

修复方向与边界

在退避返回后、下一次 operation 前重新检查预算,保留最后错误和已完成尝试数。此预算 hook 约束同一 Provider 的重试,保持现有 fallback 策略;不扩大为整个路由/Agent 运行的终止策略,也不提前中断 sleep。

当前开放 PR #242 修改同文件的 cache key 区域,#281 修改错误分类表,#274 修改路由缓存门禁;均未处理退避后的预算判定。

Codex 在用户授权下准备报告和修复。

Activity

  1. huyengiang101086-spec commented on Oct 12, 2026

    @huyengiang101086-spec
    ContributorAuthor

    /claim

  2. github-actions commented on Oct 12, 2026

    @github-actions

    @huyengiang101086-spec 已认领该 Issue,并已自动设为 Assignee。提交 PR 时请在描述中写 Closes #320。

  3. added a commit that references this issue on Oct 13, 2026
    d8fd910
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

claimedClaimed by a contributor and currently in progress

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions