现象
桌面端「同步新增」(agent source scan)始终提示「扫描 Codex 失败,请稍后重试」,且永不自愈——每次扫描都失败,即使没有任何新对话。
日志(~/Library/Logs/Memmy/memory.log)中对应大量 409:
POST /api/v1/memory/add → 409 conflict
"idempotency key reused with different request body"
实测本地累计 287 条(9/4 起持续出现),key 全部是 memory.add:agent-source:codex:agent-source-skill:... 形态。
根因
Skill 层记忆写入的请求构造(App/backend/src/services/agent-source-service.ts,scanSkills 内):
requestId: `agent-source-skill:${sourceId}:${skill.sourceSkillId}:${skill.sourceContentHash}`,
// ...
createdAt: skill.updatedAt, // ← = SKILL.md 的文件 mtime(skill-distribution-service.ts: fileStat.mtime.toISOString())
服务端幂等校验(Memory/src/server/http.ts + memory-service.ts#idempotent):
key = operation:adapterId:requestId(requestId 含 contentHash,内容不变则 key 不变)
requestHash = sha256(整个请求体)
- key 相同 + hash 不同 → 409
矛盾在于:requestId 承诺「内容指纹」,请求体却包含不受内容控制的 createdAt(文件 mtime)。
触发场景(实测):Codex 更新时重写了 ~/.codex/skills/*/SKILL.md——内容完全相同(contentHash 不变),但 mtime 变了。下次扫描:
requestId 相同(内容没变)
请求体 createdAt = 新 mtime ≠ 存量 hash 时的旧 mtime
→ 同 key 不同 body → 409 → 扫描整体报错
实测证据:imagegen skill 的 contentHash 自 8/28 起未变(幂等表 8/28 已有该 hash 的记录),文件 mtime 却是 9/4 10:05(Codex 当天更新),9/4 起日志出现 162 条 409。
由于扫描会全量重试所有 skill,冲突键永不清理,错误永久存在。
复现步骤
- 正常完成一次 Codex 扫描(skills 全部入库)
touch ~/.codex/skills/<任意skill>/SKILL.md(不改内容)
- 桌面端点「同步新增」→ 扫描 Codex 失败;日志出现 409
修复建议
agent-source-service.ts 的 skill addMemory 请求移除 createdAt 字段(它是可选字段,服务端不传时用接收时间兜底)。移除后请求体完全由内容决定,与 requestId 语义一致:
- 内容变 → contentHash 变 → 新 key → 正常入库
- 内容不变 → 请求体完全相同 →
duplicate: true,正常去重
已在本地验证完整修复链路(源码修改 + 清理冲突幂等键后,复刻扫描子进程全量跑:错误数 38 → 0,38 条消息正常去重跳过)。
附加建议(可选,双保险)
服务端 idempotent() 的 fingerprint 也可以考虑排除 createdAt 这类时间字段——mtime 语义上不该参与「同 key 不同 body」的判定。客户端修复已足够解决本 bug,此项供讨论。
环境
- Memmy 桌面端 1.1.0(macOS arm64)
- 扫描源:Codex(
~/.codex/skills/,约 90 个 skill)
现象
桌面端「同步新增」(agent source scan)始终提示「扫描 Codex 失败,请稍后重试」,且永不自愈——每次扫描都失败,即使没有任何新对话。
日志(
~/Library/Logs/Memmy/memory.log)中对应大量 409:实测本地累计 287 条(9/4 起持续出现),key 全部是
memory.add:agent-source:codex:agent-source-skill:...形态。根因
Skill 层记忆写入的请求构造(
App/backend/src/services/agent-source-service.ts,scanSkills内):服务端幂等校验(
Memory/src/server/http.ts+memory-service.ts#idempotent):key = operation:adapterId:requestId(requestId 含 contentHash,内容不变则 key 不变)requestHash = sha256(整个请求体)矛盾在于:requestId 承诺「内容指纹」,请求体却包含不受内容控制的
createdAt(文件 mtime)。触发场景(实测):Codex 更新时重写了
~/.codex/skills/*/SKILL.md——内容完全相同(contentHash 不变),但 mtime 变了。下次扫描:实测证据:imagegen skill 的 contentHash 自 8/28 起未变(幂等表 8/28 已有该 hash 的记录),文件 mtime 却是 9/4 10:05(Codex 当天更新),9/4 起日志出现 162 条 409。
由于扫描会全量重试所有 skill,冲突键永不清理,错误永久存在。
复现步骤
touch ~/.codex/skills/<任意skill>/SKILL.md(不改内容)修复建议
agent-source-service.ts的 skill addMemory 请求移除createdAt字段(它是可选字段,服务端不传时用接收时间兜底)。移除后请求体完全由内容决定,与 requestId 语义一致:duplicate: true,正常去重已在本地验证完整修复链路(源码修改 + 清理冲突幂等键后,复刻扫描子进程全量跑:错误数 38 → 0,38 条消息正常去重跳过)。
附加建议(可选,双保险)
服务端
idempotent()的 fingerprint 也可以考虑排除createdAt这类时间字段——mtime 语义上不该参与「同 key 不同 body」的判定。客户端修复已足够解决本 bug,此项供讨论。环境
~/.codex/skills/,约 90 个 skill)