fix: 修复 SDK result 后流卡死导致的误判 idle 超时#255
Conversation
|
Claude finished @lishuceo's task in 1m 19s —— View job Claude Code Review
✅ Approved — looks good to mergeVerdict: 修复方向正确、改动最小、回归测试充分。可以合并。 修复正确性
回归测试质量
PR 描述里还说明了「临时把 一些可选的小改进(不阻塞合并)
|
线上案例:idle timeout 日志中 lastResetSource=msg:result:success、 elapsedMs=851539(14分钟),说明 query 在约第 4 分钟已发出 result(success) 终止消息,但 SDK 偶发未关闭异步迭代器,for-await 继续阻塞等待永不到来的下一条 消息,600s 后被 idle timer 误判超时并 abort,把一个已经成功的 query 当成错误 抛给用户。 修复:给消息循环加 messageLoop 标签,case 'result' 处理后 break messageLoop 立即结束循环(result 是 query 的终止消息,其后不会再有有效消息),不再依赖 SDK 自然关闭流。break 加标签确保跳出 for-await 而非仅 switch。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
构造 SDK 在发出 result(success) 后 next() 永不 resolve 的迭代器,验证 execute() 仍能立即返回成功结果且只读取 init+result 两条消息。该测试在修复 前会因第三次 next() 永久阻塞而超时失败,修复后通过。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
22d8ba1 to
d29a0a5
Compare
|
Claude finished @lishuceo's task in 48s —— View job ✅ Approved — safe to mergeVerdict: 修复方向正确、改动最小、回归测试设计严谨。 修复正确性 (
|
采纳 review 建议:for-await 通过 break 提前退出时会调用 iterator.return(), 触发 SDK 内部清理。补一条 expect(returnSpy).toHaveBeenCalled() 让该语义在 测试中显式可见,而非仅靠 nextCallCount===2 间接推断。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Addressed — 已采纳建议 #1:回归测试补充 建议 #2( |
|
Claude finished @lishuceo's task in 57s —— View job ✅ Approved — safe to mergeVerdict: 修复方向正确、改动最小、新增的 本次同步新增(commit 5327d5a)
const returnSpy = vi.fn(() => Promise.resolve({ value: undefined, done: true }));
mockQueryInstance[Symbol.asyncIterator].mockReturnValue({
next: () => { /* ... */ },
return: returnSpy,
});
// ...
expect(returnSpy).toHaveBeenCalled();这条断言把"
未来 SDK 升级时如果不慎引入"break 不调 return()"之类的回归,这个断言能直接抓住。 修复正确性复核(src/claude/executor.ts:987,1125-1132)
回应作者对建议 #2、#3 的说明完全同意:
文件影响汇总
可以合。 |
问题
黎叔反馈 query 频繁报 idle timeout。查线上日志(
feishu-claude-out.log)定位到一次超时记录:{"msg":"Claude query idle timeout — no SDK message received, aborting", "idleTimeoutMs":600000,"hasToolActivity":true, "lastResetSource":"msg:result:success","elapsedMs":851539}关键信号
lastResetSource: "msg:result:success":超时前最后一次活动是result(success)消息。也就是说 query 在约第 4.2 分钟就已经发出终止消息、逻辑上成功了,但for await循环没有结束,空转了整整 600s 才被 idle timer 误判超时 abort,最终把一个已成功的 query 当成错误抛给用户。根因
executor.ts的消息循环在case 'result'里只存下resultMessage就继续for await,依赖 SDK 的异步迭代器自然关闭。但 SDK 偶发在发出result后不关闭迭代器,循环就一直阻塞等待永不到来的下一条消息 → idle timer 600s 后误杀。修复
messageLoop:标签case 'result'处理后break messageLoop立即跳出循环(result是 query 的终止消息,其后不会再有有效消息),不再依赖 SDK 自然关闭流break加标签确保跳出for-await而非仅switch测试
新增回归测试:构造「SDK 发出
result后next()永不 resolve」的迭代器,验证execute()仍能立即返回成功结果、且只读取 init+result 两条消息。已验证该测试能抓住 bug:临时把
break messageLoop退回普通break(旧逻辑),测试因第三次next()永久阻塞而超时失败;恢复修复后通过。npx vitest run src/claude/__tests__/executor.test.ts→ 34 passednpm run typecheck→ 通过memory/quality.test.ts1 项失败,系缺少DASHSCOPE_API_KEY的环境性失败,与本改动无关🤖 Generated with Claude Code