Skip to content

[Bug] 扫描 Codex 全量历史必现失败:扫描子进程 V8 Heap OOM 崩溃(SIGABRT, node::OOMErrorHandler) #311

Description

@crosswyb

环境
Memmy 版本:1.1.1(桌面版,darwin-arm64,cn-signed)
系统:macOS 26.5.2 (25F84),Apple Silicon
数据规模(~/.codex/sessions):
会话文件总数:1,687 个(含 .bak-*.jsonl 备份,会被误识别为真实会话)
总大小:20 GB(单文件最大 1.7 GB)
消息总数(扫描器实测):546,102 条,平均每条内容 ~38 KB
问题描述
在「Memory → Sources」中对 Codex 执行全量扫描,每次必现失败。扫描进行约 85–100 秒后停止,界面提示「扫描 Codex 失败,请稍后重试」,进度停留在 546,087–546,102 条消息(即恰好等于 Codex 全部消息总数)。

同一环境下:

其他 Agent(Hermes 1,331 条、DeepSeek Harness 1,637 条、Claude Code 40 条等)扫描正常;
Codex 的增量扫描(默认模式)正常:可导入新增消息并进入摘要阶段;
只有 Codex 全量扫描(mode=full,或首次无水位/无限制时)必现失败。
复现步骤
打开 Memmy 桌面版,进入「Memory → Sources」。
在 Codex 一栏点击全量扫描(或任意触发 sourceId=codex, mode=full 的扫描)。
等待约 90 秒,扫描停止并提示失败。
(可选)通过本地 API 复现:
bash
复制
curl -X POST http://127.0.0.1:51160/api/agent-sources/scan
-H "x-memmy-local-token: <runtime.json 中的 localToken>"
-H "content-type: application/json"
-d '{"sourceId":"codex","mode":"full"}'
轮询 GET /api/agent-sources/scan/status 可观察到: {"active":false,"progress":{"sourceId":"codex","phase":"stopped","current":546102,"message":"Agent source scan stopped"},"completion":{"succeeded":false}}

实际结果
扫描中途停止,completion.succeeded = false,Codex 全量历史(54.6 万条)无法导入。
每次失败时刻对应生成一份崩溃报告:
复制
~/Library/Logs/DiagnosticReports/Memmy Helper-2026-08-27-171433.ips
exception: EXC_CRASH, SIGABRT (Abort trap: 6)
faulting thread 调用栈关键帧:
node::OOMErrorHandler(char const*, v8::OOMDetails const&)
v8::TryCatch::HasTerminated() ...
abort()
即扫描子进程因 V8 JavaScript 堆内存耗尽 而崩溃。 ~/Library/Logs/Memmy/main.log 中每次失败对应一条 memory_source_scan_failed 事件。

预期结果
全量扫描应能完成,或至少分批/流式导入而不崩溃;
失败时 UI 应给出具体错误(如「内存不足」),而不是笼统的「扫描失败」。
根因分析(附源码位置,基于 1.1.1 打包产物 app.asar)
全量收集无上限、全部驻留内存 node_modules/@memmy/backend/dist/src/services/agent-source-service.js → collectSourceMessages(): for await (const message of adapter.scan({...})) { collected.messages.push(message); ... } mode=full 时 maxMessages / maxScanTargets 均为 undefined,54.6 万条消息全部 push 进内存数组,且 conversationIds.includes() 为 O(n²) 线性查重。

导入阶段再次复制放大 同文件 ingestCollectedSource() 中 sortMessagesForIngestion、groupMessagesByConversation、conversationContentHash 均会整表复制。

扫描子进程未调大 V8 堆 node_modules/@memmy/backend/dist/src/adapters/inbound/local-api/routes/agent-sources.js → createScanProcessEnvironment() 仅注入 ELECTRON_RUN_AS_NODE=1,未设置 --max-old-space-size 或 NODE_OPTIONS;fork 出的扫描子进程(agent-source-scan-process.js)使用 V8 默认堆上限(约 2–4 GB)。

内存需求量化 上表 546,102 条消息,仅 content 文本以 UTF-16 计约 40 GB,加上对象结构、排序、分组拷贝约 120 GB,远超默认堆上限,必然 OOM。

次要问题:备份文件被当作真实会话 codex/session-discovery.js 的 listRolloutFiles() 匹配 startsWith("rollout-") && endsWith(".jsonl"),会把 rollout-.jsonl.bak-.jsonl(实测存在 1.3 GB 的 -strip-input-image 备份)也纳入全量扫描,进一步放大数据量。

Memmy-Helper-crash-2026-08-27-171433_副本.md

修复建议
根治:ingestCollected / collect 改为流式导入(分批 add 到 memory service 后再丢弃),避免全量驻留;conversationIds 查重改用 Set。
短期:fork 扫描子进程时注入 NODE_OPTIONS=--max-old-space-size=8192(或按剩余物理内存计算)。
改进:session-discovery.js 排除 .bak-* 文件;扫描失败时通过 completion.results[].errors[].reason 回传真实错误信息到 UI。

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions