[ISSUE-0042] 第 8 轮 agent — scan 工具盲区暴露 4 个新 bug¶
v0.10.61 第 7 轮 agent CONVERGED 后,第 8 轮独立 agent 验证:仍未收敛。
关键发现:
scan:error-handling工具结构性盲区 — 只扫 Promise chain 一级形态(.then/.catch),完全没扫: 1.try { ... } catch (e) {}一级形态 2..finally(() => resolve())包 sendMessage(强制 resolve 即便 throw) 3. 裸 sendMessage 无任何 .then/.catch 包装
4 个 bug 修复¶
Bug R1 (🔴 高):task-view delete 单/批量同款 — .finally(resolve) 强制 resolve¶
src/sections/task/task-view.tsx:235-258 (handleDelete) + 262-288 (handleBatchDelete):
// ❌ v0.10.61
await new Promise<void>((resolve) => {
browser.runtime
.sendMessage({ type: 'task-control', taskId: id, action: 'delete' })
.finally(() => resolve()); // 永远 resolve
});
setTasks(prev => prev.filter(t => t.id !== id)); // 后端失败也移除本地
后果: 1. 后端删除失败 → 用户看到本地任务消失(toast 成功) → 下次轮询又出现 → 「幽灵任务」复现 2. 批量删除时一个失败 → 全部仍 toast 成功 → 数据/UI 不一致
修:用 try/await + 显式查 resp.success 模板(同 control() 函数)。批量改用每个 id 独立 try/catch 收集结果,按真实结果汇报。
Bug Y1 (🟡 中):storePageOneData try/catch 静默吞错¶
src/entrypoints/background/batch-controller.ts:431-453 + 翻页路径 605-614:
// ❌
try {
const res: any = await addSearchData(list, filterCfg, taskId);
if (res?.data) { notifyDataUpdated(); manageQueue(); }
if (taskId && keyword) {
recordTaskProgress(taskId, keyword, added).catch(() => {});
}
} catch (e) {} // 一页数据丢失 silent
修:至少 console.error 留痕 + recordTaskProgress 的 .catch 也加 console.error(v0.10.60 修了它的 silent_catch 但 caller 还是吞)。
Bug Y2 (🟡 中):popup-data 失败显示为真零(ISSUE-0037 变体)¶
src/sections/popup/popup-data.ts:50-53:
// ❌
.catch(() => {
setSnapshot((s) => ({ ...s, loading: false })); // 只关 loading,counts/tasks 默认 0
});
后果:popup loading 关,但商家/邮箱/任务数全显示 0 — 用户以为真没数据。
修:snapshot 接口加 error?: string,UI 区分"真零"和"加载失败"。
Bug Y3 (🟡 中):copyId 假 toast — clipboard.writeText 是 Promise¶
src/sections/task/task-view.tsx:343-348:
// ❌
const copyId = (id: string) => {
try {
navigator.clipboard.writeText(id); // Promise,不 await
notice.success('已复制任务 ID'); // 立即 toast 即便失败
} catch (e) {}
};
后果:clipboard 权限被拒/HTTPS 不达标时 writeText reject,但 toast 已显示成功 — toast lies。
修:改 async + await writeText,success 移到 await 之后;catch 显式 toast.error。
scan 工具结构性盲区(元洞察)¶
第 8 轮 agent 直接戳破:
"scan:error-handling 只扫 3 类
.then/.catch链式形态,完全没扫 try/catch、.finally 吞错、和裸调三种形态"
3 类新规则加入 scripts/scan-error-handling.py(v0.10.62 起 6 类):
| 规则 | 形态 | 命中阈值 |
|---|---|---|
finally_resolve_swallow |
.finally(() => resolve()) |
总归 user-action |
try_catch_empty |
} catch (e) {} |
上 8 行内有 IO 关键词(await/sendMessage/fetch/storage/writeText/navigator) |
bare_sendmessage |
整行 sendMessage 无 .then/.catch/.finally 下 3 行也不接 | 启发式分级 |
完整扫一遍:57 命中(旧 45 + 新 12),更新基线后 --diff 0 新增。
audit_grep 三道闸(本文 frontmatter)¶
audit_grep:
- pattern: "\\.\\s*finally\\(\\s*\\(\\s*\\)\\s*=>\\s*resolve\\(\\s*\\)\\s*\\)"
description: ".finally(()=>resolve()) 包 sendMessage 模式"
- pattern: "\\}\\s*catch\\s*(\\([^)]*\\))?\\s*\\{\\s*\\}"
description: "} catch (e) {} 一级空 catch"
- pattern: "navigator\\.clipboard\\.writeText\\([^)]+\\)\\s*;\\s*\\n[^}]*notice\\.success"
description: "writeText 不 await + 立即 success toast"
元规则演进¶
| ISSUE | 沉淀 | 防的家族 |
|---|---|---|
| 0034~0039 | 一个个修 | 错误传播家族 |
| 0040 | 第 6 轮 agent + audit_grep 反查 | "修触发点漏姊妹" |
| 0040~0041 | 5 个 scan 工具 + 修bug全字典扫描 rule | 工具化扫描 |
| 0041 | 第 7 轮 agent — scan 盲区初现(4 处漏) | 工具仅扫已知模式 |
| 0042 | 第 8 轮 agent — scan 盲区结构性(4 处漏 + 3 新规则) | 工具的"假设"也是盲区 |
第 8 轮终极洞察:
工具凝固已知模式 → 工具本身的「假设性形态」也是盲区。 scan:error-handling 在写出来时假设错误处理都通过 Promise chain, 但 JS 有 try/catch + Promise + callback 三种范式 — 跨范式的同义反模式工具看不到。
工作流推论¶
每隔 N 轮 ISSUE,独立 agent 不止要扫新 bug,还要扫现有 scan 工具能不能找到这些 bug。 找不到的 → 扩工具或承认这是手动审查范畴。 这一步不做 → 下轮还会找出新一批同范式漏。