baa-conductor

git clone 

commit
01c3a35
parent
0915a3e
author
claude@macbookpro
date
2026-03-30 16:25:25 +0800 CST
docs: add BUG-029, BUG-030, BUG-031 for renewal conversation link issues

BUG-029: findConversationLinkByRemoteConversation missing is_active filter
BUG-030: scoreConversationLink weak signals can outrank targetId
BUG-031: DEFAULT_LINK_SCAN_LIMIT silent truncation risk

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
4 files changed,  +189, -0
 1@@ -0,0 +1,66 @@
 2+# BUG-029: findConversationLinkByRemoteConversation 未过滤 is_active,续命可能打到已放弃的对话
 3+
 4+> 提交者:Claude
 5+> 日期:2026-03-30
 6+
 7+## 现象
 8+
 9+`observeRenewalConversation` 在解析已有对话关联时,优先通过 `findConversationLinkByRemoteConversation` 按 `platform + remote_conversation_id` 查找 link。该方法不过滤 `is_active`,会返回已被 deactivate 的 link。
10+
11+后续流程用这个 link 的 `localConversationId` 去关联新消息,导致续命消息被打到一个已经被主动放弃的旧对话上。
12+
13+## 触发路径
14+
15+```text
16+1. 用户在某平台创建对话 A,observe 写入 link_1 (remote_conversation_id=X, is_active=1)
17+2. 用户在 UI 上切换对话 / deactivate,link_1 被设为 is_active=0
18+3. 新消息到达同一 remote_conversation_id=X
19+4. observeRenewalConversation -> resolveExistingConversationLink
20+   -> findConversationLinkByRemoteConversation(platform, X)
21+   -> 返回 link_1(is_active=0)
22+5. 流程用 link_1.localConversationId 关联新消息 -> 续命打到旧对话
23+```
24+
25+## 根因
26+
27+`packages/artifact-db/src/store.ts` 中 `findConversationLinkByRemoteConversation` 的 SQL 只有:
28+
29+```sql
30+SELECT *
31+FROM conversation_links
32+WHERE platform = ?
33+  AND remote_conversation_id = ?
34+LIMIT 1;
35+```
36+
37+缺少 `AND is_active = 1` 条件。
38+
39+## 修复建议
40+
41+在查询中加入 `AND is_active = 1`:
42+
43+```sql
44+SELECT *
45+FROM conversation_links
46+WHERE platform = ?
47+  AND remote_conversation_id = ?
48+  AND is_active = 1
49+LIMIT 1;
50+```
51+
52+如果存在需要查已停用 link 的场景,可加一个可选参数 `includeInactive?: boolean`。
53+
54+## 严重程度
55+
56+**High**
57+
58+这是数据正确性 bug,会导致续命自动化对错误的对话发消息,是必现的逻辑错误。
59+
60+## 发现时间
61+
62+`2026-03-30 by Claude`
63+
64+## 相关代码
65+
66+- 查询方法:[packages/artifact-db/src/store.ts](/Users/george/code/baa-conductor/packages/artifact-db/src/store.ts) `findConversationLinkByRemoteConversation`
67+- 调用方:[apps/conductor-daemon/src/renewal/conversations.ts](/Users/george/code/baa-conductor/apps/conductor-daemon/src/renewal/conversations.ts) `resolveExistingConversationLink`
 1@@ -0,0 +1,67 @@
 2+# BUG-030: scoreConversationLink 累加分数允许弱信号叠加超过 targetId 强信号
 3+
 4+> 提交者:Claude
 5+> 日期:2026-03-30
 6+
 7+## 现象
 8+
 9+`resolveExistingConversationLink` 在多个候选 link 中按分数选最优。当前评分逻辑为累加制:
10+
11+```
12+targetId  匹配 +100
13+pageUrl   匹配 +80
14+routePath 匹配 +60
15+pageTitle 匹配 +10
16+```
17+
18+如果一个候选项没有匹配 `targetId`,但同时匹配了 `pageUrl + routePath + pageTitle = 150`,则其分数 150 > 100,会胜过另一个只匹配 `targetId` 的候选。
19+
20+在用户同一平台上同时开着多个对话 tab 时,旧 tab 的 URL 和 routePath 碰巧匹配新对话的概率不低,会导致绑定到错误的 `localConversation`。
21+
22+## 触发路径
23+
24+```text
25+1. 用户有两个 chatgpt tab:
26+   - link_A: targetId=tab:1, pageUrl=/c/abc, routePath=/c/abc
27+   - link_B: targetId=tab:2, pageUrl=/c/abc, routePath=/c/abc, pageTitle="My Chat"
28+2. 新 observe 来自 tab:1,但 link_B 的 pageUrl + routePath + pageTitle 全部匹配
29+3. scoreConversationLink:
30+   - link_A: targetId 匹配 = 100
31+   - link_B: pageUrl(80) + routePath(60) + pageTitle(10) = 150
32+4. link_B 胜出 -> 新消息绑定到 link_B 的 localConversation
33+```
34+
35+## 根因
36+
37+`apps/conductor-daemon/src/renewal/conversations.ts` 中 `scoreConversationLink` 用累加制打分,没有让最强信号(`targetId`)绝对优先。
38+
39+## 修复建议
40+
41+### 方案 A(推荐)
42+
43+把 `targetId` 的分数拉高到不可能被叠加超过的值:
44+
45+```typescript
46+if (observedRoute.targetId != null && candidate.targetId === observedRoute.targetId) {
47+  score += 1000;
48+}
49+```
50+
51+### 方案 B
52+
53+改成瀑布式匹配:如果 `targetId` 匹配则直接返回,不再计算其他维度。
54+
55+## 严重程度
56+
57+**Medium**
58+
59+多 tab 场景下偶发,但一旦触发会导致对话关联错乱。
60+
61+## 发现时间
62+
63+`2026-03-30 by Claude`
64+
65+## 相关代码
66+
67+- 评分函数:[apps/conductor-daemon/src/renewal/conversations.ts](/Users/george/code/baa-conductor/apps/conductor-daemon/src/renewal/conversations.ts) `scoreConversationLink`
68+- 调用方:同文件 `resolveExistingConversationLink`
 1@@ -0,0 +1,53 @@
 2+# BUG-031: DEFAULT_LINK_SCAN_LIMIT 静默截断可能导致冲突 link 未被停用
 3+
 4+> 提交者:Claude
 5+> 日期:2026-03-30
 6+
 7+## 现象
 8+
 9+`deactivateConflictingConversationLinks` 和 `resolveExistingConversationLink` 都使用 `DEFAULT_LINK_SCAN_LIMIT = 50` 作为查询上限。如果某个 `platform + clientId` 组合下的活跃 link 数超过 50 条,超出部分不会被扫描到:
10+
11+- 冲突 link 不会被 deactivate,导致同一 target 出现多个活跃 link
12+- resolve 可能漏掉真正的最佳候选,取决于排序碰巧返回哪 50 条
13+
14+没有任何日志或警告表明截断已发生。
15+
16+## 触发条件
17+
18+长期运行的 conductor 实例 + 多 client 连接 + 频繁切换对话,使同一 platform 下的活跃 link 累积超过 50 条。
19+
20+短期内可能不触发,但随使用时间增长概率递增。
21+
22+## 根因
23+
24+`apps/conductor-daemon/src/renewal/conversations.ts` 中硬编码 `DEFAULT_LINK_SCAN_LIMIT = 50`,且查询后未检测结果是否触达 limit。
25+
26+## 修复建议
27+
28+### 短期
29+
30+在 `deactivateConflictingConversationLinks` 和 `resolveExistingConversationLink` 中,当 `results.length >= limit` 时输出 warn 日志,提示可能存在截断:
31+
32+```typescript
33+if (activeLinks.length >= DEFAULT_LINK_SCAN_LIMIT) {
34+  context.log({ stage: "link_scan_truncated", details: { limit: DEFAULT_LINK_SCAN_LIMIT } });
35+}
36+```
37+
38+### 长期
39+
40+考虑对 deactivate 使用分页循环扫描,或者在 resolve 路径上使用更精确的 SQL 查询(按 targetId / pageUrl 直接定位)而非全量扫描后 in-memory 打分。
41+
42+## 严重程度
43+
44+**Low**(定时炸弹,短期有余裕)
45+
46+## 发现时间
47+
48+`2026-03-30 by Claude`
49+
50+## 相关代码
51+
52+- 常量定义:[apps/conductor-daemon/src/renewal/conversations.ts](/Users/george/code/baa-conductor/apps/conductor-daemon/src/renewal/conversations.ts) `DEFAULT_LINK_SCAN_LIMIT`
53+- deactivate 调用:同文件 `deactivateConflictingConversationLinks`
54+- resolve 调用:同文件 `resolveExistingConversationLink`
M bugs/README.md
+3, -0
 1@@ -16,6 +16,9 @@ bugs/
 2 - `BUG-026`:[`BUG-026-artifact-repo-root-fallback-broken.md`](./BUG-026-artifact-repo-root-fallback-broken.md)
 3 - `BUG-027`:[`BUG-027-startup-plugin-diagnostic-events-lost-before-ws-open.md`](./BUG-027-startup-plugin-diagnostic-events-lost-before-ws-open.md)
 4 - `BUG-028`:[`BUG-028-gemini-shell-final-message-raw-protocol.md`](./BUG-028-gemini-shell-final-message-raw-protocol.md)
 5+- `BUG-029`:[`BUG-029-find-link-by-remote-ignores-is-active.md`](./BUG-029-find-link-by-remote-ignores-is-active.md)
 6+- `BUG-030`:[`BUG-030-score-conversation-link-weak-signals-beat-strong.md`](./BUG-030-score-conversation-link-weak-signals-beat-strong.md)
 7+- `BUG-031`:[`BUG-031-link-scan-limit-silent-truncation.md`](./BUG-031-link-scan-limit-silent-truncation.md)
 8 - `OPT-002`:[`OPT-002-executor-timeout.md`](./OPT-002-executor-timeout.md)
 9 - `OPT-003`:[`OPT-003-policy-configurable.md`](./OPT-003-policy-configurable.md)
10 - `OPT-004`:[`OPT-004-final-message-claude-sse-fallback.md`](./OPT-004-final-message-claude-sse-fallback.md)