关于 session-recover
session-recover 是面向 Claude Code 用户的技能,用于处理同一项目因工作目录路径不同而分散的会话和记忆。它会识别重复命名空间、合并或评估记忆,并归档而非删除转录。
主要功能
- 01查找重复的项目命名空间
- 02确定规范记忆路径
- 03合并或评估记忆文件
- 04归档孤立的会话转录
- 05核对文件数量
适用场景
- 01恢复当前会话缺失的记忆
- 02找回 /resume 中预期的会话
- 03整合符号链接或迁移路径的命名空间
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ ngmeyer/ skills --skill session-recover安装后在 Claude Code 中输入/session-recover确认已加载。
附带文件references/merge-example.mdscripts/inventory.sh
详细介绍
/session-recover — 手动合并重复的 Claude Code 项目目录
Claude Code 会将每个项目的会话记录和持久记忆存储在 ~/.claude/projects/{encoded-cwd}/ 下。编码后的 cwd 是绝对路径,其中每个非字母数字字符都会替换为 -。两个指向同一逻辑项目的不同 cwd 路径(例如真实目录及其符号链接镜像、外部挂载路径及其 ~/Projects/ 快捷路径、Linux 用户从 /home/me 迁移到 /Users/me 等)会生成两个完全独立的命名空间——/resume 中的会话列表彼此独立,记忆目录也彼此独立,互相不可见。
此技能用于清理这些重复项:检测重复目录,将记忆统一到规范位置,归档孤立的会话记录,并记录经验教训,避免后续会话再次产生分裂。
适用场景
- 你打开了一个会话,发现记忆目录为空,但记得以前的会话中添加过条目
claude --resume显示的会话比你预期的少- 你发现两个
~/.claude/projects/-*-foo/目录的末尾标记相同 - 对话中引用的跨会话记忆无法解析(
[[some-memory]]链接无效) - 在
~/.claude/projects/中搜索项目名称时返回多个匹配项
如果以上情况都不符合,此技能无需执行——提前退出并告知用户。
核心原则
规范路径是 Claude Code 当前系统提示词告知你的路径。 当运行环境启动会话时,它会提示:
你有一个基于文件的持久记忆系统,位于
/Users/me/.claude/projects/{ENCODED}/memory/
这是胜出目录。其他所有末尾标记相似的目录都是候选落败目录。用户可以指定其他路径作为规范路径,但默认采用运行环境选定的路径。
操作步骤
阶段 1 — 识别:查找重复的项目目录
将可选参数用作项目名称标记。若未提供,则根据 cwd 的末尾部分(basename "$PWD")或对话内容推断。
bash ~/.claude/skills/session-recover/scripts/inventory.sh <project-token>
脚本会列出每个 ~/.claude/projects/*<token>*/ 目录,并显示:
- jsonl 会话文件数量 + 总大小 + 最新文件的修改时间
- 记忆目录中的条目数量
- 编码后的路径是否属于 Lexar(
Volumes-Lexar)、主目录下的 Projects(Users-.*-Projects)或其他类型
遇到以下情况时停止并询问用户:
- 只有一个匹配项 → 没有重复项 → 退出
- 存在三个或更多匹配项 → 继续之前确认哪个目录是规范目录
- 候选目录的 jsonl 修改时间差异很大(例如,一个上周活跃,另一个今天才活跃)→ 确认用户是否希望将两者合并
阶段 2 — 识别:选出胜出目录
默认使用当前会话的系统提示词中 “基于文件的持久记忆系统,位于……” 所提及的路径。
如果该路径不在重复目录候选项中(例如,用户在项目外调用此技能),则询问用户想将哪个目录设为规范目录。通常,这会是用户开始工作时实际执行 cd 进入的路径,或项目级 CLAUDE.md 中标注为“规范”的路径。
阶段 3 — 合并记忆
对于每个落败目录,将其中的记忆内容移至胜出目录。始终使用 mv,绝不使用 cp,这样落败目录的记忆目录会被清空,不再形成互相竞争的信息来源。
WINNER=~/.claude/projects/<canonical-encoded-path>
LOSER=~/.claude/projects/<loser-encoded-path>
mkdir -p "$WINNER/memory"
mv "$LOSER/memory/"* "$WINNER/memory/" 2>/dev/null
rmdir "$LOSER/memory" 2>/dev/null
**冲突处理:**如果两边存在同名文件(最常见的是 MEMORY.md),不要覆盖。读取两份文件,手动编写统一版本并保存到胜出目录,然后删除落败目录中的副本。这通常只影响 MEMORY.md(索引文件)。
阶段 4 — 归档 jsonl 会话记录
将落败目录中的 jsonl 会话文件及任何会话子目录移至 ~/.claude/archive/。不要使用 rm——归档可撤销,删除则不可撤销。
ARCHIVE_DIR=~/.claude/archive/$(basename "$LOSER")
mkdir -p "$ARCHIVE_DIR"
mv "$LOSER"/*.jsonl "$ARCHIVE_DIR/" 2>/dev/null
# Also archive any UUID subdirs (per-session checkpoint dirs)
for d in "$LOSER"/[0-9a-f]*-[0-9a-f]*/; do
[ -d "$d" ] && mv "$d" "$ARCHIVE_DIR/"
done
rmdir "$LOSER" 2>/dev/null
如果 rmdir "$LOSER" 因目录非空而失败,请列出其中剩余的内容并询问用户——通常是某个遗留文件(.DS_Store、非标准子目录)需要明确处理。
阶段 5 — 将经验记录为记忆
在胜出方的记忆目录中写入一条 feedback 类型的记忆条目,让未来的会话知道此项目存在双 cwd 隐患:
---
name: dual-cwd-memory-split-{project}
description: Memory dir is path-derived from cwd. Two cwd paths for the same project produced separate memory namespaces; merged YYYY-MM-DD.
metadata:
type: feedback
---
The {project} project is reachable via:
- `{canonical-cwd-path}` — canonical
- `{loser-cwd-path}` — symlink/mirror/alias
These produce separate `~/.claude/projects/-*/` namespaces. Memory was merged into the canonical path on YYYY-MM-DD; jsonl transcripts archived to `~/.claude/archive/{loser-encoded}/`.
**How to apply:** always `cd` into `{canonical-cwd-path}` before `claude`. If a future session lands at the other path and finds an empty memory dir, re-run /session-recover before doing real work.
然后在胜出方的记忆目录中的 MEMORY.md 添加一行指针:
- [Dual-cwd memory split](feedback_dual_cwd_memory_split_{project}.md) — memory namespaces merged YYYY-MM-DD; cd into the canonical path
阶段 6 — 验证
ls "$WINNER/memory/" | wc -l # entry count, should be sum of both sides minus dedup
ls "$LOSER" 2>/dev/null # should report "No such file or directory"
ls ~/.claude/archive/$(basename "$LOSER")/ # archived jsonls + dirs
(V2)协调核对门槛 — 证明没有遗漏。 在宣布成功之前,核对数量:在阶段 3 中、移动之前记录 winner_before 和 loser_count,然后断言 winner_after == winner_before + loser_count − dedup_count,其中 dedup_count 是你手动合并的同名文件数。如果算术结果对不上,停止并说明差异——缺少文件意味着静默丢失,而这是此技能绝不能导致的结果。只有在数量核对一致(或用户接受了有记录的去重)后,才能报告成功。
将最终状态以 3 行表格报告给用户,其中包括核对行(winner_before + loser − dedup = winner_after)。
阶段 7 — 建议运行 /compact
Claude Code 的 /compact 斜杠命令会将当前会话整理成简洁的摘要。合并后,当前对话中包含许多“发现了这个、移动了那个”的细节,之后不会再有用。告诉用户运行 /compact(你无法自行调用——这是用户级命令)。
变体:旧版孤立记忆(评估,不要盲目合并)
上面的阶段 3–4 假设 loser 是同一当前工作的实时镜像,因此将其记忆移入 winner 是安全的。但有时孤立项是项目文件夹迁移前的旧命名空间(例如,项目数月来一直位于 /Users/me/Projects/foo,后来移到了 /Volumes/Drive/Projects/foo)。此时,孤立项的记忆是旧的——包含迁移前的状态、已完成的积压事项和已被取代的事实。**盲目将其合并到规范目录,会让陈旧内容再次污染当前记忆。**如果孤立项记忆文件的修改时间比规范目录早数周,或者孤立项的 MEMORY.md 描述的显然是项目较早的状态,就应识别出这种情况。
此时,将阶段 3 中“全部 mv”的做法替换为逐文件评估:
- 阅读每个孤立的记忆文件。 对每个文件进行分类(根据当前仓库/代码核实其中的主张——文件会移动,功能会发布,事实会变化):
- 保留项 → 仓库:远程 Agent/协作者需要了解、但尚未记录在仓库(
docs/、CLAUDE.md)或规范记忆中的决策、注意事项或产品想法。→ 将其写入仓库(存入docs/decisions/下的 ADR、docs/知识文档或创意积压清单)。 - 保留项 → 本地:仍然有效的行为指南(“不要执行 X,因为它已经处理好了”)。→ 复制到规范记忆目录并编入索引。
- 秘密位置:任何说明令牌/凭据存放位置的内容,或环境到环境的映射。→ 将其纳入本地秘密清单记忆。绝不要提交,即使是提交到私有仓库也不行。
- 过时项:已被取代的状态、已完成的积压事项、旧审计。→ 归档。
- 保留项 → 仓库:远程 Agent/协作者需要了解、但尚未记录在仓库(
- 恢复保留项,将其放回对应位置(仓库或规范记忆);协调核对秘密位置,将其纳入本地清单。
- 将过时的孤立文件归档到孤立记忆目录内的
_archived-pre-migration/子文件夹中——不要删除,也不要合并到规范目录。 - 为孤立项的
MEMORY.md添加墓碑说明:将其替换为一条说明,注明这是旧路径<old-path>的孤立镜像,规范命名空间和仓库是事实来源,且文件已归档。这样可以避免未来从旧路径启动的会话信任过时内容。 - 报告一张表格:孤立文件 → 判定 → 操作。显著指出任何确实有价值的恢复项——这是此流程的收获(例如,一个从未加入积压清单的搁置产品想法)。
跳过阶段 4(对话记录归档——除非有人要求,否则不要处理旧对话记录)和阶段 5(仍值得在规范目录中写入双 cwd 反馈记忆,以免再次出现分叉)。
此技能不会做什么
- 不会将两个 jsonl 转录合并为一个会话。 这是不可能的——Claude Code 没有合并操作。此技能会保留活动会话的转录,并归档其他转录。
- 除非用户明确要求作为后续操作,否则不会编辑代码、CLAUDE.md 或计划文档。 仅处理记忆和 jsonl。如果项目中有过时的计划/状态文档需要反映合并结果,用户可以在技能运行后要求你更新。
- 不会删除任何内容。 归档可以撤销;
rm不行。 - 不会在每次调用时都运行。 如果第 1 阶段未发现重复项,就提前退出并说明情况。不要凭空制造工作。
注意事项
-
mv与cp: 始终使用mv。如果使用cp后忘了删除源文件,随着后续会话写入新条目,败方的记忆目录会继续变化。在源目录删除文件才能阻止这种情况。 -
MEMORY.md 合并冲突: 索引文件通常在两边都存在。不要盲目使用
mv——mv -n会将败方副本保留为同级文件,结果就会有两个索引。读取两边内容,手动合并,然后删除败方副本。 -
每个会话的 UUID 子目录: Claude Code 会在
<uuid>.jsonl文件旁创建<uuid>/检查点目录。这些目录大多用于可恢复会话状态。将它们与 jsonl 一并归档;不要留下孤立目录。 -
rmdir "$LOSER"失败: 通常是因为 macOS 上有.DS_Store,或存在多余的todos/目录。重试前先列出内容;不要不加判断就使用rm -rf。 -
当前会话可能位于败方目录中。 如果用户从非规范工作目录打开会话,当前
.jsonl就会写入败方目录。不要在对话过程中将其归档——正在运行的会话会无法写入。可以先让用户运行/exit,或者跳过当前会话的 jsonl,仅归档其余内容。 -
跨项目名称冲突: 如果两个项目名称包含相同的词元(例如
api和api-gateway),清单脚本会同时返回两者。移动任何内容前,务必向用户展示完整的候选列表并确认。 -
不要试图聪明地判断“哪个转录更新”。 这是合并内容的问题,而不是合并状态的问题。此技能的任务是统一记忆命名空间;活动会话的转录仍保留在原处。
更新日志
V2(2026-05-27)
通过 skillforge optimize 优化。坦诚说明: 外部结果研究不足——这是针对 Claude Code 某种特定机制的流程型技能,没有值得借鉴的前沿成果。需要保护的真正结果是合并时不丢失任何记忆,因此 V2 的改动是加强安全性,而非引入研究成果:
- 核对关卡(第 6 阶段)——断言
winner_after == winner_before + loser − dedup;任何不匹配都要停止。将“看起来完成了”变成“可证明没有丢失任何内容”。这与现有的“归档而非删除”规则相辅相成(合并本身已可撤销)。 - 没有强行加入由结果研究驱动的内容(Agent 记忆的三层模型已体现在旧版孤立项变体的 repo/local/secret 路由中)。
另请参阅
references/merge-example.md— 双工作目录合并的脱敏端到端演练。scripts/inventory.sh— 第 1 阶段的辅助脚本。- 旧版孤立项变体会将保留项恢复到仓库中(ADR、文档、创意待办),用于仓库侧迁移;此技能负责清理命名空间并创建墓碑。