Skip to content

fix(minimax-bot): 坏文件不再让插件起不来,账本写失败不再带走已经跑完的回合 - #110

Merged
MyPrototypeWhat merged 2 commits into
MiniMax-AI:mainfrom
Apageoflove:fix/minimax-bot-store-recovery-ledger
Nov 6, 2026
Merged

MyPrototypeWhat merged 2 commits into
MiniMax-AI:mainfrom
Apageoflove:fix/minimax-bot-store-recovery-ledger

Conversation

@Apageoflove

@Apageoflove Apageoflove commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

这一版改两件事。读不出来的状态文件不再让整个插件起不来,账本写不进去也不再带走已经跑完的回合。

state.json 或任意一条聊天文件读不出来时(0 字节、写到一半被截断),Store.init 直接抛 SyntaxError,server.start 在 listen 之前就 reject,整个 Mini App 起不来,界面上什么都不出来。现在读不出来的文件改名成 *.corrupt-<时间戳> 留着不删,这次从默认值起,一个聊天文件坏就那一个聊天空,其它照常加载。成因也一起修掉,写盘原来是 writeFile 加 rename,rename 之前没有把数据刷到盘上,掉电时 rename 已经提交而数据页还没落盘,就会留下一个 0 字节的文件,现在 rename 之前先 fsync,能刷目录的地方再刷一次目录。

台账的写入是裸的 appendFile,而它挂在三个关键路径上,分别是回合的收尾、群一轮发言的循环、后台作业的收尾。磁盘满了或者 usage.jsonl 不可写的时候,已经生成的回复被判成失败、群里后面的人不再发言、跑完的后台作业的报告永久丢失。现在写入失败只记一条 warn,别的一概照旧,回复保住自己的结果,群继续往下走,后台作业照常存状态、照常投递报告。

改动很小。store.mjs 有一处读取兜底、一处写盘顺序、一处写入捕获,engine.mjs 两处调用加捕获,server.mjs 一处把日志交给 store。版本三处同改到 0.7.24,README 加了一段 0.7.24。

新增两个测试文件共七条用例,坏文件留在原地、其余聊天照常加载、形状不对的状态也能起来,以及账本写不进去时回合仍然完成、群仍然往下走、后台作业仍然投递报告。这七条在未改的 0.7.21 上全是红的。插件自带套件 386 条全过,上游 npm run check 两半也过。

另外改了 tests/unknown-zombie.test.mjs 的收尾。那个用例在 dispose 之后直接删数据目录,而被中止的回合还会补一次写盘,删除和写盘撞上了,清理报 ENOTEMPTY。收尾改成先等引擎停下来、等写链排空,再删目录,和同文件里另一个用例以及服务端的关闭流程一个写法,断言一个字没动。

真实产品里跑过两种故障。把 state.json 换成 0 字节后重启,插件照常起来,坏文件留在原地,之前的聊天文件没被牵连。把 usage.jsonl 换成目录后连发四条消息,账本每一条都写不进去,四条里三条照常回复,另一条模型那一层没产出,宿主会话 0 条消息,同样条件下再发就正常,这条没有复现,仅个人判断它不在这个改动里,也没有写进代码。


Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.

读不出来的状态文件改成改名留证、从默认值起,写盘在 rename 之前先 fsync;台账写
不进去时只记一条 warn,不再把已经跑完的回合、群里后面的成员和跑完的后台报告一
并带走。版本 0.7.24。

@MyPrototypeWhat MyPrototypeWhat left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. The recovery path keeps the unreadable bytes instead of deleting them, the flush before the rename is the right fix for the 0-byte file, and the ledger guard sits at both call sites as well as inside the store.

Verified

  • readJson only quarantines on a parse failure. A read error other than ENOENT still throws, so a permission problem is not mistaken for a corrupt file. The renamed file stays next to the original inside the plugin's data directory, and the path is built from the store's own fixed paths, not from input.
  • writeJsonAtomic writes and syncs the temp file before the rename. If the write or the sync fails, the temp file is removed and the error propagates, so the previous state.json stays untouched. The directory sync is best effort, which is fine for Windows.
  • A failed ledger append is logged as minimax_bot.usage_write_failed with only the error code, and the turn id is not claimed, so a later write can still record it. The guards in runBotTurn and the background job path keep the reply status, the next group member, and the job's saved status and report.
  • The shape checks in init (bots, groups, schedules, sessions) reset only a field that has the wrong type.
  • No new reads or writes outside the data directory. The new tests live in the plugin's tests/ folder.
  • Version 0.7.24 matches in package.json, .minimax-plugin/plugin.json, the MCP serverInfo and the README, above main's 0.7.21. The check job is green at e5882a3, and the branch merges cleanly.

Optional

  • In readJson, a failed rename of the corrupt file is swallowed and the defaults are returned. The next save() then writes over state.json, and the evidence the README promises is gone. Consider copying the bytes to the .corrupt- file when the rename fails, or skipping the save in that case.
  • Quarantining a file is silent today. A warn with the file name when it happens would make a reset team or an empty chat explainable from the log, the same way the ledger miss is. The store already has the logger for it.
  • .corrupt-<time> files are never cleaned up. That is fine for now, but a cap on how many are kept would stop repeated failures from piling them up.

main now carries 0.7.23 (the hand-over carries the conclusions of the round). This merge keeps
that section and this branch's 0.7.24 section, and keeps the version at 0.7.24 in the three
places that carry it.

It also picks up the two optional points of the review: a failed rename of an unreadable file
now falls back to a copy, so the bytes stay even when the name cannot change, and the
quarantine says so in the log, so a team that came back empty is explainable.
@Apageoflove

Copy link
Copy Markdown
Contributor Author

main 上 0.7.23 合进来之后我把分支合了一次,版本仍是 0.7.24,README 里 0.7.20、0.7.21、0.7.23、0.7.24 四段按顺序各一次,没有冲突标记。用的是合并提交而不是强推,那条 APPROVED 还挂在原来那个提交上。

评审里的三条 optional 我改了两条。改名失败时现在退回复制,字节留在那里,原文件就算被下一次保存盖掉,副本还在。隔离的时候也会在日志里留一条,写着是哪个文件,这样「团队空了」或者「聊天读不出来」从日志能看出来,和账本那条一个路子。

第三条我没做。给 .corrupt 文件加个数上限是产品取舍而不是一条小修,你说现在这样也行,我就留着以后再说;要是以后重复失败把它们堆起来了,这条也好接。

门禁重跑:插件套件 394 条全过,上游 npm run check 两半也过;那两个新测试在未改的 0.7.23 上仍然是红的。

真实宿主里跑过的是改动本体,把 state.json 换成 0 字节、把 usage.jsonl 换成目录,各跑一遍。那次跑的是合并 main 之前的构建,合并之后没有再跑一遍,新增的复制兜底和那条日志也没在真实宿主里验过,这条未验证。

@MyPrototypeWhat MyPrototypeWhat left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. Thanks for merging main and picking up the two notes on evidence and the log.

Verified

  • The merge of main keeps the 0.7.23 hand-over work from #108. handoverSummary / renderHandover are in place, handover-note.test.mjs is present, and there are no conflict markers. The recovery path, the flush before the rename, and the ledger guards in runBotTurn and the background job path are still there.
  • When a file cannot be parsed, readJson still only quarantines on a JSON parse failure. A read error other than ENOENT still throws. If the rename to *.corrupt-<time> fails, it copies the bytes to that name so the next save cannot erase the only copy. The copy stays next to the original, inside the plugin's data directory, and the path is built from the store's own fixed paths, not from input.
  • Quarantine now logs minimax_bot.corrupt_file_quarantined with only the file name, so a reset team or an empty chat can be explained from the log the same way a ledger miss is.
  • A failed ledger append is still logged as minimax_bot.usage_write_failed with only the error code, and the turn id is not claimed. The reply status, the next group member, and a finished job's saved status and report are unchanged.
  • No new reads or writes outside the data directory. The new tests live in the plugin's tests/ folder.
  • Version 0.7.24 matches in package.json, .minimax-plugin/plugin.json, the MCP serverInfo and the README, above main's 0.7.23. The README keeps the 0.7.20, 0.7.21, 0.7.23 and 0.7.24 sections, each once. The check job is green at 1885cc5, and the branch merges cleanly.

Optional

  • A cap on how many .corrupt-<time> files are kept is still left for later, as noted.
  • The recovery tests still check that a corrupt file is left behind after a successful rename. They do not cover a failed rename that has to copy, or assert that the quarantine warning is written.

@MyPrototypeWhat
MyPrototypeWhat merged commit 513895a into MiniMax-AI:main Nov 6, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants