cc-haha/release-notes/v0.5.20.md
你的姓名 37ffca0ec0 docs(release): include #79 in v0.5.20 notes
PR #79 (suppress consecutive identical retry errors) merged after the
v0.5.20 release commit but before the tag was pushed. The released
build will contain its code, so the notes need to mention it. Title
broadened to cover both retry-link denoise changes (#78 + #79).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-19 10:17:31 +08:00

4.7 KiB
Raw Blame History

v0.5.20

重试链路两处去噪子代理限流器从单门改双门正常多步工作不再被误杀HTTP 重试期间相同错误不再每次都堆到对话气泡。

修复

  • 子代理限流器双门#78。原 verification 类子代理 cap=55 个连续 PASS 的 PR 验证后第 6 个就被硬阻断——即使每次都成功。源码注释承诺「only fires on pathological loops, not on normal multi-step work」实际行为违背了这条契约。本版把单门改成双门

    • 总 cap 放宽verification 5 → 12。日常多 PR 工作有充足预算。
    • 连续 FAIL streak 门:新增 noteOutcome(agentType, verdict)。3 次连续 FAIL无 PASS 中断)立即 cap与总数无关。任何 PASS / UNKNOWN 清零 streak。这才是真正抓 verifier-loop 的语义。
    • 保守 verdict 解析parseVerdict(text) 解析子代理输出的 VERDICT: PASS|FAIL|CHANGES_NEEDED|APPROVE|UNCONFIRMED 行,附带 SECURITY: PLAN_REVIEW: ROOT CAUSE: 同义词。UNKNOWN 当 soft-PASS ── 一次性研究类子代理(如 Explore没有 verdict 结构的输出不会被误杀。
    • 错误文案区分 cappedReason: 'total' | 'streak' | 'none',用户能看到具体哪个门触发。
    • 环境变量出口保留:CLAUDE_CODE_AGENT_LIMIT_<TYPE>=N 调高 total cap、CLAUDE_CODE_AGENT_LIMITER_OFF=1 整体禁用。
  • HTTP 重试期间相同错误不再刷屏对话#79。原 withRetry 默认 maxRetries=10,每次失败只要错误是 APIError 就把 SystemAPIErrorMessage yield 到对话流。一次 flaky 上游恢复需要 2-3 次重试时,对话气泡里就会堆出 2-3 条一模一样的错误用户原话「100% 显示出来」)。本版加同错误去重门控:

    • 首次出现的错误 key:立即 yield携带新信息
    • 连续相同错误:沉默到累计 ≥ 3 次(默认)才再 yield。
    • 任何 429 rate-limit:始终 yield —— wait-time 信息是它存在的意义。
    • 错误 key:用 Error.name + APIError.status + Error.message 拼接401 与 503 即使 message 相同也不会折叠。
    • State 是 generator 闭包局部变量,跨 withRetry 调用不泄露persistent 模式 heartbeat 复用一次性算定的结果,避免长 persistent 等待重复增计数。
    • 环境变量 CLAUDE_CODE_RETRY_REPORT_AFTER=N 可调阈值(默认 3
    • 效果:一段 3 次相同错误的重试现在显示 2 个气泡而非 3 个;更长 streak 压缩更明显,但用户始终能看到通话还在重试。

测试

  • src/tools/AgentTool/invocationLimiter.test.ts28 cases / 153 expects 全过。
    • 14 个原测试按新 cap=12 适配
    • 5 个 streak 行为新测试3 FAIL trip / PASS clears / UNKNOWN soft-PASS / per-agent-type 隔离 / 12 PASS 触 total cap not streak
    • 9 个 parseVerdict 测试empty / 标准 VERDICT 行 / FAIL 同义词 / APPROVE / 专家技能同义词 / 无 verdict / markdown 容忍 / 长 input tail
  • src/services/api/withRetry.test.ts4 cases3 个新测试覆盖 same-error 抑制、新 key 立即 yield、env override 提高阈值。
  • 两个 PR 各自的 verification 子代理均独立 PASS含状态隔离、heartbeat 放大、UNKNOWN soft-PASS 攻击面、throw/abort 场景 streak 残留等对抗性 probe。

设计审查

  • UNKNOWN 当 soft-PASS 的攻击面#78恶意子代理可省略 VERDICT 行避开 streak但 total cap=12 仍兜底;且子代理 prompt 由调用方完全控制,不是普通输入面。
  • throw / abort 场景的 streak 状态#78noteInvocation 已增计数但 noteOutcome 不跑——streak 保持原值不偏移,下次调用基于既有 streak 起步PASS 仍能清零。
  • 接入点对称性#78AgentTool.tsxnoteOutcomenoteInvocation 用完全相同的 guard (!toolUseContext.agentId && isBuiltInAgent(selectedAgent) && !isLimiterDisabled()),避免 streak/total 计数不同步。
  • 非 APIError 不进对话流#79Stream ended without receiving any events 这类普通 Error 仍走外层 CannotRetryError 路径,本 PR 不扩大 SystemAPIErrorMessage 的类型契约(它要求 error: APIError)。改契约会扩散到所有消费者,超出本 PR 范围。

范围

  • src/tools/AgentTool/invocationLimiter.ts:核心改造,加 streak 状态、noteOutcome、parseVerdict、双门判定。
  • src/tools/AgentTool/AgentTool.tsxcompleted 分支加 noteOutcome 调用。
  • src/tools/AgentTool/invocationLimiter.test.ts14→28 测试。
  • src/services/api/withRetry.ts:加 DEFAULT_RETRY_REPORT_THRESHOLD / errorIdentityKey / shouldReportRetry2 处 yield 改 guard。
  • src/services/api/withRetry.test.ts1→4 测试。