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

418 lines
24 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 宿主会话诊断日志洪水 —— 根因与一行修复
> 2026-10-01 · 取证方式:解包 `app.asar`(317 MB)定位源码 + 实测会话日志行级统计
> 结论一句话:**97.7% 的日志是同一个「工具调用流式帧」被逐帧记录;排除集合漏了两个 key,补上即减掉约 96% 体积。**
---
## 一、现象
会话跑久了,宿主界面会**静默哑掉**:工具调用记录在界面上不再出现,用户零感知;同时磁盘上出现大量 10 MiB 整的日志文件。
宿主自身的报错(`<配置根>/logs/daemon.log`):
```
[conversations] diagnostic log write failed {"code":"EPERM","droppedLines":2445,"droppedBytes":657690}
```
实测计数:`daemon.log` 47 次、`daemon.old.log` 62 次。
---
## 二、日志到底写了什么(实测行级统计)
样本:`logs/2026-10-01/sdk/conversations/5f607d3e-*.log` 尾段 **16,159 行 / 4 MB**。
| 行标签 | 占比 |
|---|---|
| `event-machine:dispatch` | **97.72%** |
| `state-machine:transition` | 0.99% |
| `method:requests` / `method:requests:result` | 各 0.25% |
| `artifact:*` / `LOCAL_HTTP_CONNECTION` / 其余 | < 1% |
在 `event-machine:dispatch` 内部,**95.83% 是同一条记录**,逐字相同、只有 `requestId` 在变:
```json
{"instanceId":"ci-1","input":"tool_call_update",
"requestId":"01a0f524bb5f7360bbd0fde2f4b2ea16",
"output":{"completeAssistantStream":false,"hasUsage":false,
"hasTitle":false,"resourceEffectCount":0}}
```
单行 244 B(含换行 245 B),四个字段恒为假值。**即:一次工具调用的入参是流式增量,每来一帧就落一行。**
### 2.1 帧是"零信息"的(2026-10-01 17:5x 补测,4 个会话全量统计)
帧的**完整字段**只有四个:`instanceId`、`input`、`requestId`、`output`。
- **没有** `seq`、`messageId`、`textLen`、`textHash`(39,999 帧里 "无此字段" 39,999 条);
- `output` 的四个位(`completeAssistantStream` / `hasUsage` / `hasTitle` / `resourceEffectCount`)**恒为 false / 0**;
- 同一轮次内,所有帧**逐字节相同**,只有 `requestId` 随轮次变化。
⇒ **每一帧都不携带任何可读信息**,即使退一步"要保留工具调用记录",这些帧也没有保留价值。
### 2.2 帧数是"活动量"的函数,不是定时心跳(补测)
`requestId` 的实际粒度是**一轮对话**(一次模型请求),一轮内会做很多次工具调用。实测四会话:
| 会话 | 工具调用 | 帧数 | 帧/次 |
|---|---|---|---|
| `3f43ce71…` | 236 | 39,999 | 169.5 |
| `ee3c2d82…` | 218 | 39,965 | 183.3 |
| `ac8de40d…` | 133 | 18,473 | 138.9 |
| `6f3073c7…` | 150 | 31,489 | 209.9 |
**按轮次拆开**看,"帧/次"在 **5.2 ~ 422.5** 之间波动、中位约 **150 ~ 220**:
- `3f43ce71` 逐轮:156.8 / 185.2 / 240.2 / 131.3 / 304.0 / 112.6
- `ac8de40d` 逐轮:5.2 / 39.4 / 41.6 / 79.8 / 121.0 / 154.0 / 156.3 / 247.8 / 367.1 / 422.5
**同一轮内的帧间隔分布**(样本:一轮 20,698 帧、跨度 496 秒):
| 间隔 | <1 ms | 1–10 ms | 10–100 ms | 0.1–1 s | 1–10 s | >10 s |
|---|---|---|---|---|---|---|
| 帧数 | **12,259** | 4,998 | 3,344 | 42 | 41 | 13 |
**中位间隔 = 0 ms**,平均 41.7 帧/秒。
⇒ 结论:帧是**成串爆发**式发出的(**不是**固定节奏轮询),发出密度**正比于该轮的工具有活动量**。
所以帧数由 **「这段时间发生了多少次工具调用相关事件」** 决定 —— 调用越多、单次调用持续时间越长,帧越多。
### 2.3 归因:两件事分开看(这是本节的关键)
「一次工具调用写约 200 帧、约 48 KB」是**两个独立环节相乘**的结果,责任不在一处:
1. **帧的产生** —— 归**宿主 agent 事件机**:它把每次工具调用的状态变化/流式增量**逐条上报**,
这是协议与宿主侧的设计行为。**外部改不了它发多少条**。
- ⚠️ 与"调用方式"的关系:**只体现在"次数"上**(调用越多帧越多),
但**"每次调用 150~220 帧"这个量级不取决于我们怎么写命令**——四个不同会话都是这个量级;
逐轮的 5~422 波动来自单次调用的实际活动量。
- ⇒ **这不是"工具调用方式不对"**:同一个操作换个写法,帧照旧产生。
2. **帧的落盘** —— 归**门控代码缺陷**:按函数注释,这些流式帧**本来就该被丢掉**,
只是排除名单漏了 `tool_call` / `tool_call_update`,于是**全部写盘**。
- ⇒ **这一环才是缺陷所在**(见第三、四节)。
**⇒ 一句话**:`48 KB/次` = **帧的产生**(宿主行为,改不了,不是你调用方式的问题)
+ **帧没被过滤**(厂商缺陷,本应丢弃)。唯一的责任在第二环。
### 2.4 帧能定位到"具体哪个工具"吗?—— 不能(2026-10-01 18:0x 补测)
**帧记录里没有任何工具标识字段。** 对会话日志逐一检查:
| 字段 | 出现次数 |
|---|---|
| `toolName` | 0 |
| `tool_name` | 0 |
| `kind` | 0 |
| `rawInput` | 0 |
| `toolCallId` | 0 |
⇒ 帧只有 `instanceId` / `input` / `requestId` / `output` 四个字段,
**`requestId` 的粒度还是一整轮对话** ⇒ **事后无法把 2 万帧归到某一次具体工具调用上**,
更无法回答"是哪个工具贡献的"。**诊断日志记了 2 万行,却查不出凶手是谁** —— 这本身也是缺陷的一部分。
**能确定的是**:帧的类型是 `tool_call_update`,而这是 ACP 协议里**所有工具通用**的
"工具调用状态更新"通知(不含工具种类信息)⇒ **不是某个特定工具造成的,是所有工具都会产生**;
每个工具调用都会经历"开始 / 进行中 / 完成"等状态变化,因而必然产生若干帧。
数量差异只来自"该调用期间产生了多少次更新",与工具是什么无关。
### 2.5 与"改 json 配置""加 hook"有关系吗?—— 没有(逐条排除)
1. **源码级**:发出点只读三个**运行时参数**
(`sessionUpdate` / `replayingHistory` / `completeAssistantStream`),
**不读任何配置**。json 配置(含 MCP、settings)不在这条链路上。
2. **hook 不在同一条通道**:hook 是宿主另起的**子进程回调**,不产生 ACP 的 `tool_call` 事件,
因此不会给会话日志贡献 `tool_call_update` 帧。
⚠️ 唯一理论上的**间接**影响:若某个 hook 导致工具调用被拒绝并**重试**,
会多出"调用次数",从而按比例多出帧 —— 但这是间接的、且有界。
3. **实测反证(最强)**:四个会话跨整天(08:48 / 10:16 / 12:35 / 15:55,+08),
期间 `settings.json` 与 hook 配置**改动过多次**;但「帧/次」始终稳定在 **169.5 / 183.3 / 138.9 / 209.9**,
**无任何漂移趋势**。
⇒ 帧数**不随配置与 hook 变化**;它只随"这一轮做了多少次工具调用"变化。
### 2.6 🔴 活样本:本会话的日志此刻就是卡死的(2026-10-01 18:04 观测)
写这份报告时所处的会话,其日志文件 `3f43ce71-….log`:
- 大小 **10,485,710 B**(上限 10,485,760 B,**卡在距上限 50 字节处**)
- 最后写入时间 **16:55**;观测时刻 **18:04** ⇒ **一个多小时零写入**
- 同目录下**没有 `.log.1`** ⇒ **轮转一次都没成功过**
⇒ 也就是说:**最近一个多小时的诊断日志全部丢失**。这不是"写太多"的后果,
而是"写不进去"的后果 —— 与第 8 节第 1 条(轮转失败后永不恢复)**完全吻合**,
且**功能不受影响**(会话照常工作),只是**丧失可观测性**。
---
## 三、发出点(源码位置)
`app.asar` 偏移 ≈117,210,567:
```js
if (shouldLogEventMachineDispatch(
update?.sessionUpdate,
frameContext.replayingHistory,
result.completeAssistantStream === true))
this.init.log("event-machine:dispatch", {
input: update?.sessionUpdate,
requestId: this.init.assembler.currentId,
seq, messageId, textLen, textHash,
output: { completeAssistantStream, stateEvent, turnStopReason,
hasUsage, hasTitle, resourceEffectCount },
...(shouldLogContent() ? { textPreview: readTextPreview(update) } : {})
});
```
门控定义,偏移 ≈117,230,304:
```js
/** 环境变量开关:设为 '1' 时输出截断正文预览,并恢复 replay/流式 chunk 的逐帧 dispatch 日志。 */
function shouldLogContent() {
return process.env?.WB_CONVERSATION_LOG_CONTENT === "1";
}
var STREAM_CHUNK_UPDATES = new Set(["agent_message_chunk", "agent_thought_chunk"]);
/** 默认丢掉历史回放帧和正文流式 chunk;verbose 或 turn 收口帧仍落盘。 */
function shouldLogEventMachineDispatch(sessionUpdate, replayingHistory, completeAssistantStream) {
if (shouldLogContent()) return true;
if (replayingHistory) return false;
if (completeAssistantStream) return true;
return !STREAM_CHUNK_UPDATES.has(sessionUpdate ?? "");
}
```
---
## 四、缺陷定性
函数的注释把设计意图写得很清楚:**「默认丢掉历史回放帧和流式 chunk」**。
但排除集合只列了两种**正文**流式 chunk(`agent_message_chunk`、`agent_thought_chunk`),**漏掉了 `tool_call` 与 `tool_call_update`** —— 而工具的入参同样是流式增量,一次工具调用会产生几十到上百帧。**真正的洪水正是这两个被漏掉的 key。**
也就是说:抑制流式帧的机制已经写好了,只是漏了两个成员,等于没生效。
---
## 五、修复(一行)
```js
var STREAM_CHUNK_UPDATES = new Set([
"agent_message_chunk",
"agent_thought_chunk",
"tool_call", // ← 新增
"tool_call_update", // ← 新增
]);
```
预期效果:日志体积下降约 **96%**。
按实测约 37 KB/次工具调用折算,10 MiB 上限对应的会话寿命从 **≈280 次工具调用** 变为 **≈7,000 次** —— 正常会话再也撞不到顶。
---
## 六、为什么我们这边关不掉它
- `WB_CONVERSATION_LOG_CONTENT` 只能**放大**:设为 `1` 反而变成 verbose(正文预览 + 恢复历史回放逐帧)。**不存在"设为 0 就少记"这条路。**
- 写入器的目录(`path.join(homeDir, "logs")`)与单文件上限(`PER_FILE_DISK_MAX_BYTES = 10485760`)都是**硬编码**;上限字段虽写成可注入(`options.perFileDiskMaxBytes ?? 默认值`),但全包内**只有它自己类里出现过一次**,无任何调用方传值。
- 且"让它写不进去"=直接落进它的丢写分支(丢批 + 指数退避),**那正是"哑掉"这个故障态本身**。
⇒ 结论:**外部无法关闭,只能在上游补那两个 key。**
---
## 七、我方现状兜底(治标)
已上线 Windows 计划任务 `WorkBuddy-LogCapSweep`:每 1 分钟扫一次当天目录,把满额日志**改名挪开**(`*.stuck-<ts>`,**只改名、绝不删除**),宿主会在数十秒内重建新文件并恢复写入。
局限(已实测):正在被写入的文件**不可以**改名(`WinError 32`),所以只能在它卡住之后救;且哑掉的会话不产生任何事件,**会话内钩子永远不会触发** ⇒ 只有这个"会话外时钟"能救。
---
## 八、相邻问题(同源,建议一并看)
1. **轮转失败后永不恢复**:轮转 `rotateFile()` 是四步串行(删 `.log.3` → `.log.2→.log.3` → `.log.1→.log.2` → `.log→.log.1`),**四步全成才把计数器归零**。任一步抛错(Windows 上文件被占用即 `EPERM`)⇒ 计数器恒为满值 ⇒ 此后每次写都重试轮转、次次失败、退避封顶 30 秒、每轮只报一次警 ⇒ **该会话日志永久写不进**。建议:轮转失败时也重置计数器或降级为"换名重开",避免单次瞬时占用导致永久死锁。
2. **次要噪声源**:`operation.log` 中 529 行为 `list` 查询,且使用了 `{"page":1,"size":100000}` 这种十万量级分页;另有 `state-machine:transition` 反复记录 `PHASE_WORKING → working, valid:true`。量级远小于第 4 节的问题,但同属"零信息高频写入"。
---
## 九、在哪里改(完整副本清单)
> 🔴 **2026-10-01 17:2x 更正**:本节最初的清单把两个 CLI 明文包(`codebuddy-headless.js` / `codebuddy-lite-wb.mjs`)列为可改目标,**那是误判**。复核发现它们里面的同名集合是**另一处逻辑**(`createAcpHistoryMarker` 用的历史标记集合),而且**本来就已包含 `tool_call` / `tool_call_update`**;它们**不含** `event-machine:dispatch`,与本次问题无关。真正的补丁点**只在 `app.asar` 归档内**,下表已更正。
全量扫描 `C:\Users\Administrator\AppData\Local\Programs\WorkBuddy\resources`,该字面量在**归档 `app.asar` 内共 3 处**:
| # | 位置 | 归档内绝对偏移 | 说明 |
|---|---|---|---|
| 1 | `app.asar` | 117,230,150 | 定义(后接 `;`)—— **补丁点**|归属文件 `main/conversations.js`(主进程侧) |
| 2 | `app.asar` | 287,051,214 | 定义(后接 `;`)—— **补丁点**|归属文件 `renderer/assets/ui-docs-viewer-Bo02yf1Q.js`(渲染侧) |
| 3 | `app.asar` | 311,175,531 | 写法不同(后接 `.has(updateType)`),属另一路逻辑,**不动** |
完整的 `app.asar` 路径:
`C:\Users\Administrator\AppData\Local\Programs\WorkBuddy\resources\app.asar`
### 改法:就地等长替换(无需解包 · 无需重打包)
由于归档头部记录每个文件的 `size`/`offset`,**改长度会错位**;因此采用**等长**替换:
```
替换前(55 字节):new Set(["agent_message_chunk", "agent_thought_chunk"])
替换后(55 字节):{has:()=>true} (末尾补空格凑足长度)
```
效果:`shouldLogEventMachineDispatch()` 只在 turn 收口帧返回 `true`,逐帧噪声不再落盘。
配套要做的只有一件事:把被改文件的那条完整性记录(1 个总哈希 + 6 个分块哈希)重算回去——同样等长(64 位十六进制)。
### 已交付的可执行补丁(2026-10-01 · 17:35 修订)
桌面文件夹 `WorkBuddy-日志补丁-20261001\`,**双击即用**:
| 文件 | 作用 |
|---|---|
| `检查状态.bat` | 只看现状(补丁是否已应用 + 完整性核对),**只读不改** |
| `应用补丁.bat` | 打补丁(自动备份) |
| `恢复原始文件.bat` | 还原官方原文件 |
| `logpatch.py` | 补丁程序本体(也可单独命令行跑:`--check` / 无参=打补丁 / `--revert` / `--verify`) |
| `ui-docs-viewer-Bo02yf1Q.js.patched` | 打补丁后该 JS 的内容,供对照 |
已实测:中文路径下双击链路完整跑通(含中文输出);`--revert` 分支在临时目录用假文件验证通过(`CURRENT-PATCHED` → `ORIGINAL-BACKUP`)。
### 🔴 实装后核对,发现并修掉一个**真实缺陷**(务必看)
首轮实装后做独立核对,发现补丁的**归属判定有 bug**:
- `owner_of()` 只用 `offset <= 偏移 < offset+size` 取**第一个**命中项。而 `unpacked` 文件的头部记录是 `offset=0` + 真实 `size`(本例 `editor_sdk.exe`:`offset=0 size=209,494,568 unpacked=true`)⇒ **它把 117,230,150 那一处的归属"抢"走了**。
- 而完整性重算那一步写的是 `if not ow or ow[3]: continue` —— **遇到 `unpacked` 就跳过**。两者叠加 ⇒ 字节被改了(改对了,确实该改),但**它真正的宿主 `main/conversations.js` 的完整性记录没跟着重算**。
- 后果:该文件 `hash=MISMATCH`。虽然实测确认归档**没有**整文件级的顶层哈希、且现存 44 个 `unpacked` 文件本来就不匹配(说明这些记录当前未必被强制校验),但"记录与内容不一致"本身就是隐患,不能留。
- ⚠️ 教训:首轮的"自检全绿"是**假绿** —— 它只检查了自己重算过的那个文件,**没有反向核对"该重算的文件是否都重算了"**。
已处置:
1. **补齐归档**:按文件条目(用其唯一 `offset` 做锚点,避开 basename 歧义)就地重算 `main/conversations.js` 的 `hash` + `blocks`,**先在内存验证通过才落盘**,落盘后差异只落在头部 1 个 1 MiB 块内。
2. **修掉脚本**:`owner_packed()` 改为**排除 `unpacked`**、并在多候选取"最小者";同时去掉"遇 unpacked 就跳过"的写法,改为"找不到打包归属 ⇒ **中止不写盘**"。
3. **补上反向核对**:脚本现在会跑全量 `--verify`(只对打包文件),并在落盘前自检。
### 最终核对结果(2026-10-01 17:35 · 独立核对器)
- 归档**无**整文件级顶层哈希 ⇒ 改字节不会触发整文件校验失败。
- 与官方备份逐 1 MiB 块比对:差异**只在头部**(完整性记录)+ 2 处补丁点,无任何意外改动。
- 全量完整性:**打包文件 17,800 个全部通过,0 个不通过**。
- 两处补丁点均 `hash=OK`(`main/conversations.js` `blocks=1/1`;`ui-docs-viewer-*.js` `blocks=6/6`)。
- 旧串残留 1 处(=那处下文接 `.has(...)` 的另一路逻辑,本就不在范围);新串 2 处。
### 风险清单
1. **应用升级会覆盖改动**,每次升级都要重做(重跑 `应用补丁.bat`)。
2. 这是厂商签名的官方主程序,改动后出问题不受厂商支持;本项目红线也明确 **⛔ 不写官方主程序** ⇒ 这一步只能由使用者自行决定与执行。
3. 回滚源只有一份:`app.asar.bak-<日期>`,**请勿删除**。
---
## 十、附:可直接提交给厂商的缺陷说明(2026-10-01)
> 下面这段**自包含**,可整段复制到厂商反馈表单 / 邮件里提交,不必附带本报告其余章节。
> 提交入口与必填项 ⇒ 见本节末尾。
### 标题
会话诊断日志被流式工具帧刷爆,且轮转失败后永久停止写入(日志静默哑掉)
### 基本信息
- 产品:WorkBuddy
- 版本:**5.6.2**(Electron 37.10.3-24)
- 平台:Windows(win32 x64)
- 安装路径:`C:\Users\Administrator\AppData\Local\Programs\WorkBuddy\`
### 问题一:97.7% 的会话诊断日志是零信息的工具流式帧
**现象**:单次工具调用约写 **150~220 帧 / 约 37~48 KB** 诊断日志。一份 16,159 行的会话日志里,**97.72%** 的行标签是 `event-machine:dispatch`,其中 **95.83%** 是逐字节相同的同一条记录(只有 `requestId` 在变):
```json
{"instanceId":"ci-1","input":"tool_call_update",
"requestId":"01a0f524bb5f7360d0fde2f4b2ea16",
"output":{"completeAssistantStream":false,"hasUsage":false,"hasTitle":false,"resourceEffectCount":0}}
```
**帧是零信息的**:字段只有 `instanceId` / `input` / `requestId` / `output`;没有 `seq`、`messageId`、`textLen`、`textHash`(39,999 帧全量统计,"无此字段" 39,999 条);`output` 四个布尔位**恒为 `false`/`0`**;同一轮内所有帧逐字节相同。
**帧是成串爆发的,不是定时心跳**:同一轮内帧间隔分布 —— <1 ms 有 **12,259** 帧(**中位间隔 0 ms**)、1–10 ms 4,998、10–100 ms 3,344,平均 **41.7 帧/秒**。四会话实测「帧/次」= 169.5 / 183.3 / 138.9 / 209.9,逐轮波动 5.2~422.5。⇒ 帧数正比于**该轮工具活动量**,与调用方式无关。
**帧无法归因到具体工具**:`toolName` / `tool_name` / `kind` / `rawInput` / `toolCallId` 在会话日志中**出现次数均为 0**;`requestId` 的粒度是**一整轮对话**(一次模型请求)。⇒ 记了两万行,却查不出是哪个工具贡献的。
**根因(源码级)**:`app.asar` 内偏移 ≈**117,230,304** 处的门控函数,注释写明「默认丢掉历史回放帧和流式 chunk」,但排除集合只列了两种**正文** chunk:
```js
var STREAM_CHUNK_UPDATES = new Set(["agent_message_chunk", "agent_thought_chunk"]);
function shouldLogEventMachineDispatch(sessionUpdate, replayingHistory, completeAssistantStream) {
if (shouldLogContent()) return true;
if (replayingHistory) return false;
if (completeAssistantStream) return true;
return !STREAM_CHUNK_UPDATES.has(sessionUpdate ?? "");
}
```
**漏掉了 `tool_call` 与 `tool_call_update`** —— 而工具的入参同样是流式增量,一次工具调用会产生几十到上百帧。**真正的洪水正是这两个被漏掉的 key。** 发出点在同归档偏移 ≈**117,210,567**。
**建议修复(一行)**:
```js
var STREAM_CHUNK_UPDATES = new Set([
"agent_message_chunk",
"agent_thought_chunk",
"tool_call", // ← 新增
"tool_call_update", // ← 新增
]);
```
预期日志体积下降约 **96%**:按 37 KB/次工具调用折算,10 MiB 上限对应的会话寿命从 **≈280 次工具调用** 提升到 **≈7,000 次**,正常会话再也撞不到顶。
### 问题二:轮转失败后该会话日志永久不再写入
**现象**:磁盘上出现刚好 **10 MiB**(10,485,760 B)整的日志且**不再增长**;`daemon.log` 反复报:
```
[conversations] diagnostic log write failed {"code":"EPERM","droppedLines":2445,"droppedBytes":657690}
```
(`daemon.log` 出现 47 次、`daemon.old.log` 62 次。)
**活样本**:会话 `3f43ce71-….log` —— 大小 **10,485,710 B**(卡在距上限 **50 字节**处),最后写入时间 **16:55**,观测时刻 **18:04**(**一个多小时零写入**),同目录下**没有 `.log.1`**(轮转一次都没成功过)。
**根因**:`rotateFile()` 四步串行(删 `.log.3` → `.log.2→.log.3` → `.log.1→.log.2` → `.log→.log.1`),**只有四步全成才把已写字节计数器归零**。Windows 上任一步因文件被占用抛 `EPERM` ⇒ 计数器恒为满值 ⇒ 此后每次 flush 都重试轮转、次次失败、退避封顶 30 秒、每轮只报一次警 ⇒ **该会话日志永久写不进**。
**建议修复**:轮转任一子步失败时**也重置计数器**,或降级为"换名重开新文件",避免单次瞬时占用导致永久死锁。
### 复现步骤
1. 打开一个会话,连续执行较多工具调用(数十次以上)。
2. 观察 `<配置根>\logs\<日期>\sdk\conversations\<session_id>.log`:体积快速增长(≈37 KB/次工具调用);用文本工具统计行标签,`event-machine:dispatch` 占九成以上,且绝大多数是 `input:"tool_call_update"` 的同一记录。
3. 让该文件涨到 10 MiB(10,485,760 B)左右;此后文件不再增长,界面上的工具调用记录也不再出现(**静默哑掉**),而会话功能本身正常。
4. 查看 `logs\daemon.log`,可见 `diagnostic log write failed` 与 `EPERM`。
### 影响
- 每会话约 10 MiB 上限被零信息帧快速耗尽 ⇒ 会话正常跑几小时后诊断日志即停写;
- 停写**不报错、界面无提示**,排障时"看起来像卡死",实则丧失可观测性;
- 磁盘占用被无价值内容放大近百倍。
### 我方环境(供参考)
- `WB_CONVERSATION_LOG_CONTENT` **未设置**。按源码它只读该变量,且置 `1` 只会**放大**日志(正文预览 + 恢复历史回放逐帧)⇒ **不存在"调小"的开关**。
- 日志目录 `path.join(homeDir, "logs")` 与单文件上限 `PER_FILE_DISK_MAX_BYTES = 10485760` 均为**硬编码**;`perFileDiskMaxBytes` 虽写成可注入(`options.perFileDiskMaxBytes ?? 默认值`),但全包内**无任何调用方传值** ⇒ **外部无法调低或关闭**。
### 附件建议(提交时随附)
1. `daemon.log` 中 `[conversations] diagnostic log write failed` 相关片段;
2. 一份卡住的会话 `.log`(如 `3f43ce71-….log`)+ 同目录文件列表(证明无 `.log.1`);
3. 日志行标签占比统计的截图。
### 提交入口与必填项
- **应用内(推荐)**:右下角/左下角 **头像 → 设置 → 帮助与反馈 → 意见反馈**,粘贴上文,**勾选"上传日志"**。
- **邮件**:`[email protected]`(一般 1~2 个工作日响应;紧急可在标题前加 `【紧急】`)。
- 官方要求随附:**问题标题** · **WorkBuddy 版本**(设置 > 关于,本机为 5.6.2)· **平台**(Windows)· **问题描述** · **截图/日志** · **复现步骤** —— 均已包含在上文。
- ⚠️ 应用内"提交"这一步**必须由使用者本人点**(本机可用的反馈工具只有微信支付专用的一个,不适配产品缺陷)。
- 同款可直接复制版另存于桌面:`C:\Users\Administrator\Desktop\WorkBuddy-故障反馈-提交稿-20261001.md`。