Version line
v2 — Go 重写(1.x),main-v2(积极开发中)
Exact version
v1.15.0
What happened?
Steps to reproduce
OS / platform
Windows 11 家庭版
Relevant logs or output
# Bug Report: 切换 Tab 后对话消息回档/消失
## 严重程度
**P0 — 数据丢失**(用户消息在切换对话 Tab 后永久消失或回档到历史状态)
---
## 摘要
在 Windows 桌面端,用户从对话 A 切换到对话 B 再切回对话 A 时,对话 A 的消息会**回档到某个固定历史点**,该点之后的所有消息(包括 AI 回复和用户继续发送的消息)全部消失。问题在 goal-mode(自主执行模式)和普通聊天模式下均可复现,且用户在多个独立会话中重复遭遇此问题。
---
## 环境
| 项 | 值 |
|----|-----|
| 操作系统 | Windows 10/11 (amd64) |
| Shell | PowerShell |
| 项目路径 | `D:\API(wendang)` |
| Session 存储路径 | `C:\Users\{user}\AppData\Roaming\reasonix\projects\D--API(wendang)\sessions\` |
| Node.js | v24.16.0 |
---
## 复现步骤
1. 打开对话 A,连续发送多条消息(或启动 goal-mode 自主执行任务)
2. 不等待自动保存完成,切换到对话 B
3. 在对话 B 中操作后,切回对话 A
4. **观察**:对话 A 只显示了部分历史消息(回档到某个固定点),之后的消息全部消失
---
## 实际行为
对话 A 被"回档"到某个固定的历史快照点。例如:
- 发送了 msg1, msg2, msg3, msg4, msg5, msg6
- 切 Tab 再切回来 → 只能看到 msg1, msg2, msg3, msg4
- msg5 和 msg6 **永久消失**(JSONL 文件中不存在这些消息的任何记录)
用户在同一会话 "深度分析这套提示词,后续根据我的要求…" 中遭遇此 bug 至少 **4 次**。
---
## 文件级证据
### 证据 1:`.display.json` 与 JSONL 数据不一致
Session `20260626-030404` 的状态对比:
| 文件 | 内容 |
|------|------|
| `20260626-030404...jsonl` | 6 行,仅包含 3 条用户消息 |
| `.display.json` 中该 session 的条目 | **5 条**用户消息 hash |
`.display.json` 比 JSONL 多出的 2 条"幽灵条目":
hash: a5c63450fe97b00d...
preview: "我们的聊天中间对话框被吞掉了,这是新版的提示词,我要将格式改成:大殿上..."
hash: ecdcf71ed4121bbd...
preview: "不要出现既不是画面,又不是台词的语句"
这两条消息用户曾在 UI 中看到并发送,`.display.json` 已记录其 hash,但 **JSONL 中完全没有**。用户切 Tab 后它们消失。
### 证据 2:Session 被移入 `.trash`
在 `.trash/` 目录中发现 **5 个被删除的会话**:
20260701-102746...jsonl (23 KB, 1 条消息)
20260701-104749...jsonl (0 字节, 空文件)
20260701-104750...jsonl (0 字节, 空文件)
20260701-105214...jsonl (134 KB, 2 条消息)
20260701-112244...jsonl (121 KB, 3 条消息)
这些会话在用户不知情的情况下被移入了垃圾箱。两个 0 字节的会话表明写入操作在文件创建之后、数据写入之前即告失败。
### 证据 3:Goal-mode 会话 `.display.json` 落后
Session `20260701-115308`(goal-mode 自主执行):
- JSONL:273,090 字节,69+ 行,包含完整的工具调用链
- `.display.json`:初始只有 2 条用户消息 hash(修复前),而用户在 goal-mode 完成后又继续在该会话中发送了第 3 条消息(`f60c1ad...` "测试这个文档内的提示词"),此消息有 hash 但 JSONL 中缺失
### 证据 4:用户首次报告时间
用户最早在 `20260626-030404` 会话中就报告了此问题:
> "中间屯对话了是什么情况,中间有一些对话消失"
此后在不同会话中重复遭遇,说明这是一个**系统性 bug**,非偶发。
---
## 根因分析
### 直接原因:三文件写入非原子
Reasonix 用三个独立文件维护一个会话的状态,写入是三次独立的 I/O 操作,无事务保证:
保存时:
WriteFile(XXX.jsonl) ← 独立调用,可能失败
WriteFile(.display.json) ← 独立调用,不检查上一步结果
WriteFile(XXX.jsonl.meta) ← 独立调用
任何一个写入失败都不会回滚其他写入。
### 触发条件
1. **Windows NTFS 文件锁**:Windows 默认对正在写入的文件加排他锁。如果自动保存定时器和 Tab 切换的保存操作同时触发,其中一个会因拿不到锁而静默失败。
2. **Node.js 异步 I/O 不检查返回值**:通过 `fs.writeFile()` 异步写入,失败回调可能被忽略。
3. **Tab 切换时读取陈旧状态快照**:切换 Tab 时触发的 `saveSession()` 可能从已卸载/休眠的 UI 组件状态管理器中读取到过时的消息列表缓存,用旧快照覆盖了磁盘上的新数据。
### 两种失败模式
| 模式 | 表现 | 原因 |
|------|------|------|
| JSONL 完整,`.display.json` 落后 | 消息在磁盘但 UI 不显示 | `.display.json` 写入失败或被旧数据覆盖 |
| `.display.json` 有新条目,JSONL 缺失 | UI 里看到但磁盘没有 | JSONL 追加写入失败或被截断 |
---
## 修复建议
### 短期(应用层修复)
1. **原子写入**:用「写临时文件 → fsync → 原子 rename」替代直接覆写,避免写入中断导致文件截断
正确做法:
WriteFile(jsonl.tmp) → Flush → Rename(jsonl.tmp, jsonl)
2. **写入顺序保证**:先写 JSONL(数据源),成功后再更新 `.display.json`(索引),JSONL 失败则不更新索引
3. **重试机制**:`WriteFile` 遇到 Windows 文件锁错误(`EBUSY` / `EPERM`)时重试 3 次,每次间隔 100ms
4. **保存时从实际 UI 状态读取**:Tab 切换的 `saveSession()` 必须从当前 DOM/React state tree 读取消息列表,不能依赖可能过时的缓存快照
### 长期(架构改进)
5. **用 SQLite 替代多 JSON 文件**:SQLite 自带 ACID 事务保证,单文件、原子提交,彻底消除不一致问题
6. **添加 `.display.json` 自动修复机制**:启动时扫描所有 JSONL,重建 `.display.json`(类似 `fsck`),自动修复不一致
---
## 附加信息
- 用户遭遇此 bug 的时间跨度:2026-07-01 至 2026-07-02(两天内至少 4 次)
- 受影响的会话数:至少 3 个活跃会话
- 用户已验证:goal-mode 和普通聊天模式均受影响
- 用户已验证:回档点是固定的(每次回档到相同的历史状态)
- 用户操作系统:Windows(具体版本待确认)
- 备份文件路径:`sessions_backup_20260702\`(如需进一步调试可提供)
Version line
v2 — Go 重写(1.x),main-v2(积极开发中)
Exact version
v1.15.0
What happened?
Steps to reproduce
OS / platform
Windows 11 家庭版
Relevant logs or output