im_wower
·
2026-03-28
BUG-023-claude-final-message-not-captured-in-practice.md
1# BUG-023: Claude 有机回复在实际 Firefox 环境中仍未被 final-message 捕获
2
3> 提交者:Claude(实测验证)
4> 日期:2026-03-28
5
6## 现象
7
8BUG-022 修复后(`9f3ca77`),final-message.js 已新增 Claude 平台支持(`isRelevantStreamUrl` + `extractClaudeCandidateFromText`),自动化测试通过。但在 mini 的真实 Firefox 环境中,用户在 Claude 页面正常聊天,conductor 的 `instruction_ingest` 始终没有收到 Claude 平台的 final_message。
9
10具体表现:
11- `GET /v1/browser` 的 `instruction_ingest.recent_ingests` 全是 Gemini,0 条 Claude
12- `bridge.clients[0].final_messages` 为空数组
13- `claude.lastActivityAt` 始终为 0,说明 page-interceptor 的 SSE 事件没有到达 controller.js
14
15同时 Gemini 的 final_message 观察器正常工作(有 44 条 ingest 记录),证明 conductor 侧的 ingest pipeline 本身没问题。
16
17## 已排除的原因
18
191. **代码未更新**:`git pull` 确认 main 分支已包含 `9f3ca77`(fix: relay Claude final messages)
202. **conductor 未重启**:已重启 conductor daemon,加载了最新代码
213. **插件未重载**:已在 `about:debugging` 重载扩展
224. **凭证问题**:`request_credentials` 返回 `freshness=fresh headers=19`,API 代发正常
235. **page-interceptor 未注入**:endpoint 采集正常(45 个 Claude 端点),说明 page-interceptor 的 `emitNet` 路径在工作
24
25## 关键线索
26
27### 线索 1:Claude 的 `lastActivityAt` 始终为 0
28
29这意味着 `handlePageSse` 中的 `applyObservedClaudeSse(data, tabId)` 从未被调用。但 `handlePageNetwork` 中的 `applyObservedClaudeResponse` 在工作(endpoint 采集正常)。
30
31说明 page-interceptor 的 `emitNet`(`__baa_net__`)在工作,但 `emitSse`(`__baa_sse__`)对 Claude completion 请求没有触发。
32
33### 线索 2:tab_reload 打开新 tab 而非 reload 当前 tab
34
35`POST /v1/browser/actions {"action":"tab_reload","platform":"claude"}` 不是 reload 用户正在聊天的 tab,而是 reload 的是 shell tab(`claude.ai/#baa-shell`)。用户实际发消息的 tab(`claude.ai/chat/xxx`)不受影响,仍然运行旧版 content-script。
36
37这导致了一个根本问题:**用户聊天的 tab 和插件受管的 shell tab 不是同一个 tab**。
38
39### 线索 3:shell_runtime 显示 drift
40
41之前观察到 Claude shell_runtime 的状态:
42```json
43{
44 "actual": {
45 "exists": false,
46 "issue": "non_shell",
47 "candidate_tab_id": 16,
48 "candidate_url": "https://claude.ai/chat/xxx"
49 },
50 "drift": {
51 "aligned": false,
52 "needs_restore": true,
53 "reason": "shell_missing"
54 }
55}
56```
57
58用户在 `claude.ai/chat/xxx` 聊天,但插件认为 shell tab 应该是 `claude.ai/#baa-shell`,所以标记为 `non_shell` + `shell_missing`。
59
60## 根因假设
61
62page-interceptor 的 `isSse` 判断和 `streamSse` 函数在 Claude 页面上应该能正常工作(代码逻辑上 `pathname.endsWith("/completion")` 能匹配)。但 SSE 事件可能在以下环节断掉:
63
64### 假设 A:content-script 未注入到用户聊天 tab
65
66插件 reload 后,只有**新打开或 reload 的页面**才会注入新版 content-script。用户之前已经打开的 Claude 对话 tab 仍然运行旧版(没有 delivery-adapters.js,final-message.js 也可能是旧版或未加载)。
67
68`tab_reload` action 只操作 shell tab,不操作用户正在聊天的对话 tab。
69
70**验证方法**:在用户聊天的 Claude tab 上按 F5 手动刷新,再发消息。
71
72### 假设 B:page-interceptor 的 SSE 拦截对 Claude 不生效
73
74page-interceptor 的 `streamSse` 需要 `response.clone().body.getReader()` 来读 SSE。如果 Claude 的 fetch 请求使用了某种方式导致 response 不可 clone 或 body 不可 read,SSE 事件就不会被 emit。
75
76emitNet 能工作但 emitSse 不能工作,说明 `isSse` 返回 true 后走了 `streamSse` 分支,但 `streamSse` 内部的 reader 可能没产出任何 chunk。
77
78**验证方法**:在 Claude 页面的 devtools console 中检查 `__baa_sse__` 事件是否被 dispatch。
79
80### 假设 C:controller.js 中 FINAL_MESSAGE_HELPERS 为 null
81
82如果 final-message.js 加载失败(语法错误等),`globalThis.BAAFinalMessage` 为 undefined,controller.js 的 `FINAL_MESSAGE_HELPERS` 为 null,所有 final message 观察都会静默跳过。
83
84但 Gemini 的 final_message 在工作,说明 FINAL_MESSAGE_HELPERS 不为 null(除非 controller 页面和 Claude 页面用的是不同的 JS 上下文——但 FINAL_MESSAGE_HELPERS 是在 controller.html 中加载的,不是在 content-script 中)。
85
86## 建议排查步骤
87
881. **手动刷新用户聊天的 Claude tab**(F5),再发消息,看 `lastActivityAt` 是否变化
892. **在 Claude tab 的 devtools console** 执行 `window.dispatchEvent(new CustomEvent("__baa_sse__", {detail:{test:true}}))` 看 controller 是否收到
903. **检查 page-interceptor 的 streamSse 是否对 Claude completion 触发**:在 devtools 中查看 `window.fetch` 是否被 patch(应该是 `patchedFetch`)
914. **检查 emitSse 在 Claude 页面上是否被调用**:在 page-interceptor.js 的 `streamSse` 函数入口加 `console.log`
92
93## 严重度
94
95**Critical** — BAA 指令系统对 Claude 的端到端闭环完全依赖 final-message 观察器。不解决这个问题,整个闭环对 Claude 不工作。
96
97## 与 BUG-022 的关系
98
99BUG-022 修复了 final-message.js 缺少 Claude 支持的代码问题。BUG-023 是在代码修复后、实际 Firefox 环境中仍然不工作的运行时问题。两个 bug 的层面不同:
100- BUG-022 = 代码层(已修)
101- BUG-023 = 运行时/注入层(待排查)