[ISSUE-0041] 第 7 轮 agent 验证 v0.10.58 — 还有 4 处漏¶
v0.10.58 修了 ISSUE-0040 6 个 bug + 沉淀
scan:error-handling+ 写bug-fix-full-dictionary-scan.md后,第 7 轮独立 agent 验证仍找出 4 处 scan 工具漏抓的同家族 bug。关键发现:自动化扫描覆盖了一级模式(直接
.catch(()=>{})),但二级形态(writeChain = X.then(...).catch(()=>{})把 catch 提到链尾)完全在盲区。
4 个 bug¶
Bug 1 (高):content-search「打开搜索」按钮静默失败 — ISSUE-0040 A2 反向¶
src/sections/content-search/index.tsx:246:
但 background src/entrypoints/background/index.ts:115 是用 webext-bridge 注册的:
chrome 原生 sendMessage 不会触发 webext-bridge 的 handler,会落到下面 runtime.onMessage.addListener 的 default fallback → 用户点「打开搜索」按钮静默失败。
这是 ISSUE-0040 A2 的反向同款(A2 是「webext-bridge 调 chrome 原生」,本 bug 是「chrome 原生 调 webext-bridge」)。两个方向都漏过 — 自动化扫描没扫这个匹配关系(caller 协议 ↔ handler 协议)。
修:
// ✅ v0.10.60
import { sendMessage } from 'webext-bridge/content-script';
onClick={async () => {
const resp: any = await sendMessage('open-page-main', {}, 'background');
if (!resp?.success) {
console.warn('[content-search] open-page-main failed', resp);
}
}}
Bug 2 (中):writeChain 二级形态漏修 — page-log + task-progress¶
ISSUE-0040 A1 修了 task-store.ts 的 addTask/updateTask/removeTask 把 catch 提出来:
// ✅ task-store.ts 已经是这个形态
const next = writeChain.then(async () => { ... });
writeChain = next.catch(() => {}); // chain 自愈
return next; // caller 见错误
但 page-log.ts:46 + task-progress.ts:46/73 仍是老形态:
// ❌ v0.10.59
writeChain = writeChain
.then(async () => { ... })
.catch(() => {}); // 把错误彻底吞掉
return writeChain; // caller 拿到 resolved Promise — 永远不知错
为何 scan:error-handling 没扫到:脚本只匹配单行 .catch(() => {}),但这里的 .catch 是在 .then 链尾。hit_key 用 file+line+snippet 当 key — 链尾 catch 命中后归到 fire-and-forget 基线,所以视为合理设计直接放过了。
修:照 task-store.ts 模板 next+writeChain 分离。
Bug 3 (中):apiCountryLocations API 形状假设 — country-stats + simple-locations-select¶
umi-request 在某些拦截器配置下会把外层 { code, data } 脱掉(特别是 ISSUE-0034 修 baseConfig 之后)。
// ❌ v0.10.59 假设永远 { items: [...] }
const items = (r?.items || []) as ...;
// r 实际可能直接是 [...] → items = [] → "无数据"
命中位置:
- src/utils/country-stats.ts:59
- src/components/locations-select/simple-locations-select.tsx:79
注:
task-create-dialog.tsx:116和location-picker-dialog.tsx:308在前几轮已用Array.isArray兜底 — 这两个是漏改的同款。修了 4 处中 2 处,没扫剩下 2 处。
修:
Bug 4 (低):location-picker retryStat / loadStates React lifecycle race(v0.10.61 修)¶
MUI Dialog 默认 keepMounted=false → 关闭即卸载。两类 race:
-
unmount race:用户点 ↻ 重试国家点位 → invalidate + preloadAll 在跑 → 用户关 dialog → 异步回调 setState → React 警告 + 内存浪费。同款问题在
loadStates(apiCountryLocations.then/.catch/.finally 都 setState)。 -
stale-data race(顺手修):用户 country A→B 快速切换 → A 的 in-flight 响应后到 →
setStatesData(A's items)覆盖 B 的视图 → 一闪而过的错误数据。
修:
const aliveRef = useRef(true);
useEffect(() => {
aliveRef.current = true;
return () => { aliveRef.current = false; };
}, []);
// retryStat: 用 aliveRef 守卫
.then(() => preloadAll([iso2], (k, v) => {
if (!aliveRef.current) return;
setCountryStats((s) => ({ ...s, [k]: v }));
}))
// loadStates: aliveRef + currentLoadRef 双守卫
const currentLoadRef = useRef<string | null>(null);
const loadStates = (iso2: string, force = false) => {
currentLoadRef.current = iso2;
...
.then((r) => {
if (!aliveRef.current || currentLoadRef.current !== iso2) return;
...
})
...
};
为什么 v0.10.58 + scan 工具仍漏¶
| 漏 | scan:error-handling 盲区 |
|---|---|
| Bug 1 | 不扫 caller 协议 ↔ handler 协议匹配关系 |
| Bug 2 | 只匹配单行 .catch(()=>{}),不识别 writeChain.then(...).catch(...) 链尾形态 |
| Bug 3 | 不在 silent_catch / catch_to_empty / send_without_check 三类内 — 完全跨类 |
元洞察:自动化扫描固化了当前已知的模式。新模式(即使概念上同家族)需要新工具或扩展现有规则。
修复总结¶
| Bug | 文件 | 变更 |
|---|---|---|
| 1 | content-search/index.tsx | 改用 webext-bridge sendMessage + 检查 resp.success |
| 2a | utils/page-log.ts | next+writeChain 分离 |
| 2b | utils/task-progress.ts | 同上(recordTaskProgress + clearTaskProgress) |
| 3a | utils/country-stats.ts | Array.isArray defense |
| 3b | components/locations-select/simple-locations-select.tsx | 同上 |
| 4 | components/locations-select/location-picker-dialog.tsx | aliveRef 守 unmount + currentLoadRef 守 country 切换 race(v0.10.61) |
audit_grep 三道闸(已写入本文 frontmatter)¶
audit_grep:
- pattern: "writeChain\\s*=\\s*writeChain\\s*\\.\\s*then[\\s\\S]{0,400}\\.\\s*catch\\(\\s*\\(\\s*\\)\\s*=>\\s*\\{\\s*\\}\\s*\\)"
description: "writeChain 链尾 catch — 必须改 next+writeChain 分离"
- pattern: "browser\\.runtime\\.sendMessage\\(\\s*\\{\\s*type:\\s*['\"]open-page-main['\"]"
description: "open-page-main 必须走 webext-bridge"
- pattern: "apiCountryLocations\\([^)]*\\)[^;]*\\.\\s*then\\(\\s*\\([^)]+\\)\\s*=>\\s*[^A]*\\?\\.\\s*items"
description: "apiCountryLocations 形状假设 — Array.isArray 兜底"
后续 pnpm scan:issue-coverage 每次 pre-commit 自动反查 — 防新代码再引入。
元规则提案¶
每隔 N 轮 ISSUE,独立 agent 应专门审查 scan 工具的盲区 — 不是再找 bug,而是问「我们的工具能不能再发现这些 bug?」
工具是凝固的智慧,但凝固也是凝固 — 同家族的不同形态它认不出来。