Files
dsh_ai1net_server/交付物/宿主缺陷报告-会话诊断日志-20261001.md
admin c1b5e4d966 chore(工作区): 全量入库 + 补齐 .gitignore(以工作区为准)
- 变更规模:新增 514 / 修改 62 / 重命名 155 / 删除 4(归档重组与文档轮次)
- .gitignore 修:`归档/**/db-cwd归一-备份-*/` —— 原规则写绝对层级(归档/db-cwd归一-…),
  目录搬进 归档/配置与备份/ 后**静默失效**,43 MB 的 DB 备份又变成未跟踪
- .gitignore 补:嵌套 git 内部数据(归档/内嵌git-20261008/、归档/skills-git-旧线-20261007/dotgit-原样移出/)
- .gitignore 补:运行态与部署副本(.workbuddy/collab/、.workbuddy/tools/、.workbuddy/.load-pending、.workbuddy/tmp-*)
- .gitignore 补:备份件(*.bak-*)
- 未跟踪文件从 2190 降到 890(其余为 归档/ 归档件与 .workbuddy/memory/ 知识文件,按口径入库)
2026-10-10 23:13:22 +08:00

9.0 KiB
Raw Permalink Blame History

宿主缺陷报告 · 会话诊断日志(丢帧 → 会话静默哑掉)

提交对象:WorkBuddy 宿主(桌面版 / DSH 客户端)研发 取证环境:Windows · CODEBUDDY_CONFIG_DIR=E:\ProgramData\.workbuddy · 宿主 bundle cli/dist/codebuddy-lite-wb.mjs(Sep 21 20:37) 取证日期:2026-10-01 | 取证会话:ee3c2d82(活跃 8 分钟后达 5.0 MB,最终 7.5 MB) 性质:本报告只做观测与建议,⛔ 不涉及修改宿主任何文件(我们未改、也不建议本地改 app.asar)。


一、现象(用户可感知面)

  1. 长会话界面停止刷新、看起来像"卡死",没有任何提示。
  2. 后台协作程序向该会话投递通知(ok:true 记账成功)—— 实际全部落空,无人知晓(假绿)。
  3. 用户与 AI 双方都收不到"这个会话已经废了"的信号,只能靠人去猜。

⇒ 根因:单会话诊断日志写满上限后宿主拒写(丢行),而"丢行"这一事实没有被任何一方观测到。


二、缺陷 ① 🔴 日志 95% 以上是零信息心跳行

实测读数(会话 ee3c2d82,20,925 行 / 5.0 MB 时点)

项 值
event-machine:dispatch 行 20,464 / 20,925 = 97.8%
单行字节 249 B
真正含内容的行 461 行(2.2%)
若只记"真事件" 该文件应约 115 KB,而不是 5 MB

证据(连续多行,仅时间戳不同)

2026-10-01T04:35:42.629Z event-machine:dispatch {"instanceId":"ci-1k","input":"tool_call_update","requestId":"01a0f5beea0c7d379ed2c7fd0003baf6","output":{"completeAssistantStream":false,"hasUsage":false,"hasTitle":false,"resourceEffectCount":0}}
2026-10-01T04:35:42.8xxZ event-machine:dispatch {"instanceId":"ci-1k","input":"tool_call_update","requestId":"01a0f5beea0c7d379ed2c7fd0003baf6","output":{"completeAssistantStream":false,"hasUsage":false,"hasTitle":false,"resourceEffectCount":0}}

🔴 同一 requestId 下,四个布尔位恒为 false、requestId 也不变 ⇒ 这些行不携带任何新信息: 该行的 JSON 里根本没有 update 的载荷,只有"某帧到达了"这一个事实。诊断价值 ≈ 0。

🔴 根因已定位到源码行:STREAM_CHUNK_UPDATES 漏了 tool_call_update

位置:app.asar(主进程包,偏移 ≈117,230,304),main/conversations.js

if (shouldLogEventMachineDispatch(update?.sessionUpdate, frameContext.replayingHistory,
                                  result.completeAssistantStream === true))
    this.init.log("event-machine:dispatch", { … });

/** 默认丢掉历史回放帧和正文流式 chunk;verbose 或 turn 收口帧仍落盘。 */
function shouldLogEventMachineDispatch(sessionUpdate, replayingHistory, completeAssistantStream) {
    if (shouldLogContent()) return true;              // env WB_CONVERSATION_LOG_CONTENT === "1" ⇒ 全开
    if (replayingHistory) return false;               // 历史回放帧 ⇒ 丢
    if (completeAssistantStream) return true;         // turn 收口帧 ⇒ 必写
    return !STREAM_CHUNK_UPDATES.has(sessionUpdate ?? "");
}
var STREAM_CHUNK_UPDATES = /* @__PURE__ */ new Set(["agent_message_chunk", "agent_thought_chunk"]);

🔴 读法(关键):宿主已经有一套"高频流式帧不落盘"的机制 —— 它把 agent_message_chunk / agent_thought_chunk 两类主动丢掉(注释原文:「默认丢掉历史回放帧和正文流式 chunk」)。 但 tool_call_update 不在这个集合里 ⇒ 它逐帧全部落盘。

⇒ 而按第一节实测,tool_call_update 恰好就是占比 97.8% 的那一类(与"正文流式 chunk"完全同性质的高频帧)。

建议(最小改动 · 一行):把 tool_call_update 加进该集合(或按 requestId 做状态变化去重):

var STREAM_CHUNK_UPDATES = new Set(["agent_message_chunk", "agent_thought_chunk", "tool_call_update"]);

预期收益(按本报告量化基线推算):日志量 ↓ ≈98%(37 KB/次 → 约 0.7 KB/次) ⇒ 撞 10 MiB 的调用数 280 次 → 约 14,000 次 ⇒ "会话写爆日志而静默哑掉"这一问题基本消失。

⚠️ 本地没有可用的开关:我们完整检查了 shouldLogEventMachineDispatch 及其上游 —— 唯一相关的 env 是 WB_CONVERSATION_LOG_CONTENT=1,而它是反方向的(shouldLogContent() 为真 ⇒ 全部落盘,只会更糟)。 ⇒ 没有"静默档"可用,所以这一项只能由宿主侧修(我们不会去改 app.asar:那违反我们的红线,且升级即被覆盖)。

input 分布(同会话):tool_call_update 23,803 | session_info_update 434 | usage_update 165 | tool_call 164 | config_option_update 10 | current_mode_update 3。


三、缺陷 ② 🔴 写满上限后丢行而非轮转,且轮转成功与否不可预期

同日同目录(logs/2026-10-01/sdk/conversations/)横向对比:

会话 当前文件 轮转件 结果
cb60cb60-… 10,485,737 B 0 个 精确撞顶后停止
5f607d3e-… 10,485,717 B 0 个 同上
f8a792ab-… 10,485,715 B 0 个 同上
a202550c-… 4,355,503 B 3 个(.log.1/.2/.3,各 ≈10 MB) ✅ 正常轮转、会话存活

a202550c 的轮转时刻(文件 mtime):.log.3 00:56:07 | .log.2 01:14:59 | .log.1 01:39:13。

🔴 读法:轮转能力是存在的(上限 10,485,760 B = 10 MiB,a202550c 一天转 3 次、每转一次文件归零)。 但另外 3 个会话在完全相同的上限上零轮转 ⇒ 轮转不是必然路径,失败时静默降级为丢行。

可疑机理(未证实,需宿主侧确认):Windows 上重命名要求目标文件可被其他持有者释放 (FILE_SHARE_DELETE)。若轮转瞬间该文件仍被另一进程/句柄打开 ⇒ 轮转失败。 ⇒ 我们建议的验证:在轮转失败分支上打一条明确日志(如 ROTATE_UNAVAILABLE)—— 目前失败是完全静默的,这正是我们排查了两天的主要原因。

建议:

  1. 轮转失败 ⇒ 必须留痕(等级不低于 WARN),并退避重试;
  2. 轮转失败 ⇒ 不要把"丢行"当作正常路径:至少应向 UI 暴露"本会话诊断日志已满、已停止记录";
  3. 🔴 无论轮转成败,"日志已满/已丢行"应当是一个可被外部观测的状态(见下节)。

四、缺陷 ③ 🔴 "日志快满 / 已满"没有任何外部信号

宿主对自家 daemon 日志有指纹限流(scope+level+首参 同指纹 1 秒最多 5 条), 但单会话诊断日志是零去重、零节流、零告警;而写满后的"拒写"同样无声。

后果链:日志满 → 拒写 → UI 静默哑掉 → 协作程序投递仍记 ok:true(假绿) → 无人知晓。

建议(最小可用):

  1. 达上限时写入一条显式记录(哪怕写到另一个文件),或
  2. 对外暴露可查询的会话健康状态(如"本会话日志已停止写入"),或
  3. 直接恢复写入(轮转 + 去重),使"写满"不再是终态。

五、我们这一侧已经做的(供对照,非宿主职责)

我们无法改宿主,所以在宿主之外补了一道事前叫停: 07-scripts/session-log-guard.py —— 挂在 PostToolUse + UserPromptSubmit, 只读日志字节数(os.path.getsize,⛔ 不读内容)⇒ 软 5 MiB / 硬 8 MiB 时向该会话注入一条提醒。 🔴 它只能"叫停",⛔ 不能"降速" —— 上面三条缺陷不修,会话寿命仍然只有约 280 次工具调用。


六、量化基线(可直接用于验收宿主侧的修复效果)

指标 实测值 修复后应变为
每次工具调用日志量 144 帧 = 37,502 B 大幅下降(去掉心跳行后应 ≈1 KB/次)
调用频率(本机较忙会话) ≈20 次/分 不变
达 5 MiB 所需时间 约 8 分钟 显著拉长
撞 10 MiB 的调用次数 ≈280 次 不再是终态(轮转可靠)
日志中零信息行占比 97.8% ≈0%

换算自证:10 MiB ÷ 37 KB ≈ 283 次,与实测撞顶会话的 264~297 次吻合。


附:复现命令(只读,可在本机直接跑)

D="$CODEBUDDY_CONFIG_DIR/logs/$(date +%F)/sdk/conversations"
# ① 零信息行占比
ls -S "$D" | head -1 | xargs -I{} sh -c 'T=$(wc -l < "'"$D"'/{}"); H=$(grep -c "event-machine:dispatch" "'"$D"'/{}"); echo "$H/$T"'
# ② 每次调用的日志量
f="$D/<会话id>.log"; echo $(( $(stat -c%s "$f") / $(grep -c "\"input\":\"tool_call\"" "$f") ))
# ③ 撞顶未轮转的会话(应为空)
ls -l "$D" | awk '$5==10485737 || $5==10485717 || $5==10485715'

报告人:项目侧(会话 ee3c2d82) 关联材料:接续包_日志事前叫停_20261001.md(§1.5 / §6)|.workbuddy/memory/2026-10-01.md §四十