跳转至

[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。 找不到的 → 扩工具或承认这是手动审查范畴。 这一步不做 → 下轮还会找出新一批同范式漏。

相关

  • [[0040-round6-agent-6-bugs-incomplete-patch|0040-第6轮agent发现6个真bug-补丁不彻底家族]]
  • [[0041-round7-agent-4-misses-patch-sequel|0041-第7轮agent发现4个漏修-补丁不彻底续集]] — 第 7 轮 scan 盲区初现
  • 修bug全字典扫描 — 沉淀的规则
  • 独立agent审查 — 跨视角防 anchor bias