session-recover
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinese/session-recover — Manually merge duplicate Claude Code project directories
/session-recover — 手动合并重复的Claude Code项目目录
Claude Code stores per-project session transcripts and persistent memory under . The encoded cwd is the absolute path with every non-alphanumeric character replaced by . Two different cwd paths that point at the same logical project (a real directory + its symlink mirror, an external-mount path + a shortcut, a Linux user moving from to , etc.) produce two completely separate namespaces — separate session lists in , separate memory dirs, no cross-visibility.
~/.claude/projects/{encoded-cwd}/-~/Projects//home/me/Users/me/resumeThis skill cleans that up: detect the duplicates, unify the memory into the canonical location, archive the orphan transcripts, and capture the lesson so future sessions don't re-create the split.
Claude Code 将每个项目的会话记录和持久化内存存储在路径下。编码后的cwd是将绝对路径中的所有非字母数字字符替换为后的结果。指向同一个逻辑项目的两个不同cwd路径(例如真实目录与其符号链接镜像、外部挂载路径与快捷方式、Linux用户从迁移到等)会生成两个完全独立的命名空间——中的会话列表相互独立,内存目录也彼此分离,完全无法交叉访问。
~/.claude/projects/{encoded-cwd}/-~/Projects//home/me/Users/me/resume本技能可清理此类问题:检测重复目录,将内存统一到规范位置,归档孤立的会话记录,并记录该经验,避免未来会话再次出现拆分。
When this skill applies
适用场景
- You opened a session and the memory dir is empty, but you remember adding entries in past sessions
- shows fewer sessions than you expect
claude --resume - You find two directories whose trailing tokens match
~/.claude/projects/-*-foo/ - Cross-session memory references in conversation don't resolve (links to nothing)
[[some-memory]] - A grep for the project name across returns multiple matches
~/.claude/projects/
If none of the above hold, the skill has nothing to do — exit early and tell the user.
- 你打开会话后发现内存目录为空,但记得在之前的会话中添加过内容
- 显示的会话数量比预期少
claude --resume - 你发现两个目录,其末尾标识匹配
~/.claude/projects/-*-foo/ - 对话中的跨会话内存引用无法解析(链接指向空内容)
[[some-memory]] - 在中搜索项目名称返回多个匹配结果
~/.claude/projects/
如果以上情况都不满足,本技能无需执行——提前退出并告知用户。
Core principle
核心原则
The canonical path is the one Claude Code's current system prompt tells you it is. When the harness starts a session it announces:
You have a persistent, file-based memory system at/Users/me/.claude/projects/{ENCODED}/memory/
That's the winner. Everything else with a similar trailing token is a loser candidate. The user can override if they actually want a different path canonical, but default to what the harness chose.
规范路径是Claude Code当前系统提示中告知你的路径。当启动会话时,系统会提示:
你拥有一个基于文件的持久化内存系统,位于/Users/me/.claude/projects/{ENCODED}/memory/
这个路径就是目标路径。所有其他带有相似末尾标识的路径都是待合并候选路径。用户可以手动指定其他路径作为规范路径,但默认使用系统提示中的路径。
Procedure
操作流程
Phase 1 — IDENTIFY: find duplicate project dirs
阶段1 — 识别:查找重复项目目录
Use the optional argument as a project-name token. If omitted, infer from cwd's trailing component () or the conversation.
basename "$PWD"bash
bash ~/.claude/skills/session-recover/scripts/inventory.sh <project-token>The script lists every directory with:
~/.claude/projects/*<token>*/- jsonl session file count + total size + newest-file mtime
- memory dir entry count
- whether the encoded path appears to be Lexar (), home-Projects (
Volumes-Lexar), or something elseUsers-.*-Projects
Stop and ask the user if:
- Only one match exists → no duplicates → exit
- Three or more matches exist → confirm which is canonical before proceeding
- The candidates have wildly different jsonl mtimes (e.g. one was active last week, another was active today) → confirm the user wants both folded together
使用可选参数作为项目名称标识。如果未提供,则从当前工作目录的末尾组件()或对话内容中推断。
basename "$PWD"bash
bash ~/.claude/skills/session-recover/scripts/inventory.sh <project-token>该脚本会列出所有目录,并显示:
~/.claude/projects/*<token>*/- jsonl会话文件数量 + 总大小 + 最新文件的修改时间
- 内存目录中的条目数量
- 编码路径是否属于Lexar()、用户项目目录(
Volumes-Lexar)或其他类型Users-.*-Projects
出现以下情况时,停止操作并询问用户:
- 仅找到一个匹配项 → 无重复目录 → 退出
- 找到三个或更多匹配项 → 先确认哪个是规范路径再继续
- 候选目录的jsonl文件修改时间差异极大(例如一个上周活跃,另一个今天活跃)→ 确认用户是否需要合并两者
Phase 2 — IDENTIFY: pick the winner
阶段2 — 识别:选择目标路径
Default: the path mentioned in the current session's system prompt under "persistent, file-based memory system at …".
If that path isn't a duplicate-dir candidate (e.g. user is invoking this skill from outside the project), ask the user which one they want canonical. Usually it's the path they actually into when starting work, or the path called out as "canonical" in any project-level CLAUDE.md.
cd默认选择:当前会话系统提示中“基于文件的持久化内存系统位于……”所提及的路径。
如果该路径不在重复目录候选列表中(例如用户从项目外部调用本技能),则询问用户选择哪个路径作为规范路径。通常选择用户启动工作时实际进入的路径,或项目级CLAUDE.md中注明为“规范路径”的路径。
cdPhase 3 — MERGE memory
阶段3 — 合并内存
For each loser, move its memory contents into the winner. Always , never , so the loser's memory dir empties out and stops being a competing source of truth.
mvcpbash
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/nullConflict handling: if both sides have a file with the same name (most commonly ), do not clobber. Read both, write a unified version into the winner by hand, delete the loser's copy after. This usually only affects (the index file).
MEMORY.mdMEMORY.md对于每个待合并路径,将其内存内容移动到目标路径。始终使用而非,这样待合并路径的内存目录会被清空,不再作为竞争的数据源。
mvcpbash
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.mdMEMORY.mdPhase 4 — ARCHIVE jsonl transcripts
阶段4 — 归档jsonl会话记录
Move the loser's jsonl session files and any per-session subdirs to . Don't — archive is reversible, deletion isn't.
~/.claude/archive/rmbash
ARCHIVE_DIR=~/.claude/archive/$(basename "$LOSER")
mkdir -p "$ARCHIVE_DIR"
mv "$LOSER"/*.jsonl "$ARCHIVE_DIR/" 2>/dev/null将待合并路径的jsonl会话文件和所有会话子目录移动到。不要使用——归档是可恢复的,而删除不可逆转。
~/.claude/archive/rmbash
ARCHIVE_DIR=~/.claude/archive/$(basename "$LOSER")
mkdir -p "$ARCHIVE_DIR"
mv "$LOSER"/*.jsonl "$ARCHIVE_DIR/" 2>/dev/nullAlso archive any UUID subdirs (per-session checkpoint dirs)
同时归档所有UUID子目录(每个会话的检查点目录)
for d in "$LOSER"/[0-9a-f]-[0-9a-f]/; do
[ -d "$d" ] && mv "$d" "$ARCHIVE_DIR/"
done
rmdir "$LOSER" 2>/dev/null
If `rmdir "$LOSER"` fails because the directory isn't empty, list what's still there and ask the user — there's usually a stray file (`.DS_Store`, a non-standard subdir) that needs explicit handling.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"`执行失败(目录非空),列出剩余内容并询问用户——通常存在 stray文件(如`.DS_Store`、非标准子目录)需要手动处理。Phase 5 — CAPTURE the lesson as memory
阶段5 — 将经验记录为内存
Write a -type memory entry into the winner's memory dir so a future session knows the dual-cwd hazard exists for this project:
feedbackmarkdown
---
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.Then add a one-line pointer to in the winner's memory dir:
MEMORY.md- [Dual-cwd memory split](feedback_dual_cwd_memory_split_{project}.md) — memory namespaces merged YYYY-MM-DD; cd into the canonical path在目标路径的内存目录中写入一条类型的内存条目,以便未来会话知晓该项目存在双cwd风险:
feedbackmarkdown
---
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 pathPhase 6 — VERIFY
阶段6 — 验证
bash
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) Reconciliation gate — prove nothing was lost. Before declaring success, reconcile the counts: capture and in Phase 3 before moving, then assert , where is the number of same-name files you hand-merged. If the arithmetic doesn't close, STOP and show the discrepancy — a missing file means a silent loss, which is the one outcome this skill must never produce. Only report success once the count reconciles (or the user accepts a documented dedup).
winner_beforeloser_countwinner_after == winner_before + loser_count − dedup_countdedup_countReport the final state to the user as a 3-row table, including the reconciliation line ().
winner_before + loser − dedup = winner_afterbash
ls "$WINNER/memory/" | wc -l # 条目数量,应为双方总和减去去重数量
ls "$LOSER" 2>/dev/null # 应返回“No such file or directory”
ls ~/.claude/archive/$(basename "$LOSER")/ # 已归档的jsonl文件和目录(V2) 对账校验 gate — 确保无内容丢失。在宣布成功前,核对数量:在阶段3执行移动操作前记录和,然后验证,其中是手动合并的同名文件数量。如果等式不成立,立即停止并显示差异——文件丢失意味着静默数据损失,这是本技能绝对不能出现的结果。只有在数量核对无误(或用户接受已记录的去重操作)后,才能报告成功。
winner_beforeloser_countwinner_after == winner_before + loser_count − dedup_countdedup_count以三行表格的形式向用户报告最终状态,包括对账行()。
winner_before + loser − dedup = winner_afterPhase 7 — SUGGEST /compact
阶段7 — 建议使用/compact
Claude Code's slash command folds the running session into a clean summary. After a merge the current conversation contains a lot of "found this, moved that" detail that won't be useful later. Tell the user to run (you can't invoke it yourself — it's a user-level command).
/compact/compactClaude Code的斜杠命令可将当前会话折叠为简洁摘要。合并操作后,当前对话包含大量“找到XX、移动XX”的细节,后续无用。告知用户运行(你无法自行调用该命令——这是用户级命令)。
/compact/compactVariant: legacy orphan memory (assess, don't blind-merge)
变体:旧版孤立内存(评估而非盲目合并)
Phases 3–4 above assume the loser is a live mirror of the same current work — so moving its memory into the winner is safe. But sometimes the orphan is a legacy namespace from before a project folder moved (e.g. the project was at for months, then moved to ). Then the orphan's memory is old — pre-move status, done backlogs, superseded facts. Blind-merging it into the canonical dir re-pollutes current memory with stale content. Detect this when the orphan's memory mtimes are weeks older than the canonical dir's, or the orphan's describes a clearly earlier project state.
/Users/me/Projects/foo/Volumes/Drive/Projects/fooMEMORY.mdIn that case, replace Phase 3's -everything with a per-file assessment:
mv- Read every orphaned memory file. Classify each (verify claims against the current repo/code — files move, features ship, facts drift):
- KEEPER → repo: a decision, gotcha, or product idea a remote agent/collaborator needs that ISN'T already in the repo (,
docs/) or canonical memory. → write it into the repo (ADR underCLAUDE.md, adocs/decisions/knowledge doc, or an ideas backlog).docs/ - KEEPER → local: still-valid behavior guidance ("don't do X, it's already handled"). → copy into the canonical memory dir + index it.
- SECRET-LOCATION: anything naming where a token/credential lives, or env→environment maps. → fold into the canonical secrets-inventory memory. Never commit, even to a private repo.
- STALE: superseded status, finished backlogs, old audits. → archive.
- KEEPER → repo: a decision, gotcha, or product idea a remote agent/collaborator needs that ISN'T already in the repo (
- Recover the keepers to their destinations (repo or canonical memory); reconcile secret-locations into the local inventory.
- Archive the stale orphan files to an subfolder inside the orphan memory dir — don't delete, don't merge into canonical.
_archived-pre-migration/ - Tombstone the orphan's : replace it with a note that this is the orphaned
MEMORY.mdmirror, the canonical namespace + repo are the source of truth, and the files were archived. This stops a future session opened from the old path from trusting stale content.<old-path> - Report a table: orphaned file → verdict → action. Call out any genuinely valuable recovered item prominently — that's the payoff (e.g. a parked product idea that never made it into a backlog).
Skip Phases 4 (transcript archive — leave legacy transcripts alone unless asked) and 5 (the dual-cwd feedback memory is still worth writing in the canonical dir so the split doesn't recur).
上述阶段3-4假设待合并路径是当前工作的实时镜像——因此将其内存移动到目标路径是安全的。但有时孤立目录是项目文件夹移动前的旧版命名空间(例如项目曾在存在数月,之后移动到)。此时孤立目录的内存是旧的——包含移动前的状态、已完成的待办事项、已过时的信息。盲目合并到规范目录会使当前内存被陈旧内容污染。当孤立目录的内存文件修改时间比规范目录早数周,或其描述的是明显更早的项目状态时,可判断为这种情况。
/Users/me/Projects/foo/Volumes/Drive/Projects/fooMEMORY.md在这种情况下,将阶段3的“全部移动”替换为逐文件评估:
- 读取所有孤立内存文件。对每个文件进行分类(对照当前代码库验证内容——文件会移动、功能会发布、信息会过时):
- 保留 → 代码库:远程代理/协作者需要的决策、注意事项或产品想法,且尚未存在于代码库(、
docs/)或规范内存中。→ 写入代码库(CLAUDE.md下的ADR、docs/decisions/中的知识文档或待办想法)。docs/ - 保留 → 本地:仍有效的行为指导(“不要做X,已处理”)。→ 复制到规范内存目录并建立索引。
- 保密位置:任何提及令牌/凭证存储位置或环境映射的内容。→ 合并到规范的保密清单内存中。绝对不要提交到代码库,即使是私有库。
- 过时:已过时的状态、已完成的待办事项、旧审计记录。→ 归档。
- 保留 → 代码库:远程代理/协作者需要的决策、注意事项或产品想法,且尚未存在于代码库(
- 恢复保留的内容到目标位置(代码库或规范内存);整合保密位置信息到本地清单。
- 归档过时的孤立文件到孤立内存目录下的子文件夹——不要删除,不要合并到规范目录。
_archived-pre-migration/ - 标记孤立目录的:替换为说明,指出这是
MEMORY.md的孤立镜像,规范命名空间和代码库是数据源,文件已归档。这可防止未来从旧路径打开的会话信任陈旧内容。<old-path> - 报告表格:孤立文件 → 判定 → 操作。突出显示任何真正有价值的恢复内容——这是操作的收益(例如从未进入待办清单的搁置产品想法)。
跳过阶段4(会话记录归档——除非用户要求,否则保留旧版会话记录)和阶段5(仍需在规范目录中写入双cwd反馈记忆,避免再次出现拆分)。
What this skill does NOT do
本技能不执行的操作
- Does not merge two jsonl transcripts into a single session. That's not possible — Claude Code has no merge operation. The skill keeps the active session's transcript and archives the others.
- Does not edit code, CLAUDE.md, or plan docs unless the user explicitly asks for that as a follow-up. Memory-and-jsonls only. If the project has a stale plan/status doc that should reflect the merge, the user can ask you to update it after the skill runs.
- Does not delete anything. Archive is reversible; is not.
rm - Does not run on every invocation. If Phase 1 finds zero duplicates, exit early and say so. Don't manufacture work.
- 不会将两个jsonl会话记录合并为单个会话。这无法实现——Claude Code没有合并操作。本技能保留活跃会话的记录,归档其他记录。
- 不会编辑代码、CLAUDE.md或计划文档,除非用户明确要求后续执行此操作。仅处理内存和jsonl文件。如果项目中有过时的计划/状态文档需要反映合并情况,用户可在本技能运行后要求更新。
- 不会删除任何内容。归档是可恢复的;不可逆转。
rm - 不会在每次调用时都运行。如果阶段1未找到重复目录,提前退出并告知用户。不要无中生有地制造工作。
Gotchas
注意事项
-
vs
mv: alwayscp. If youmvand forget to delete the source, the loser's memory dir keeps drifting as future sessions land there and write new entries. Deletion at the source is what stops the bleed.cp -
MEMORY.md merge collision: the index file usually exists in both. Don'tblindly —
mvwill keep the loser's copy as a sibling and you'll end up with two indexes. Read both, hand-merge, delete loser's copy.mv -n -
Per-session UUID subdirs: Claude Code createscheckpoint dirs alongside
<uuid>/files. These are mostly resumable-session state. Archive them along with the jsonl; don't leave orphans.<uuid>.jsonl -
failing: usually a
rmdir "$LOSER"on macOS or a stray.DS_Storedir. List the contents before retrying; don'ttodos/reflexively.rm -rf -
The current session might be in the loser dir. If the user opened a session from the non-canonical cwd, the currentis being written to the loser. Don't archive it mid-conversation — the running session's writes will fail. Either tell the user to
.jsonlfirst, or skip the current session's jsonl and archive the rest./exit -
Cross-project name collisions: if two project names share a token (e.g.and
api), the inventory script will return both. Always show the user the full candidate list and confirm before moving anything.api-gateway -
Don't try to be clever about "which transcript is more recent." That's a merge-content question, not a merge-state question. The skill's job is to unify the memory namespace; the active session's transcript stays where it is.
-
vs
mv:始终使用cp。如果使用mv且忘记删除源文件,待合并路径的内存目录会随着未来会话的写入继续产生差异。删除源文件才能停止这种情况。cp -
MEMORY.md合并冲突:索引文件通常在双方都存在。不要盲目使用——
mv会将待合并路径的副本保留为同级文件,导致出现两个索引。读取两个文件,手动合并,然后删除待合并路径的副本。mv -n -
会话级UUID子目录:Claude Code会在文件旁创建
<uuid>.jsonl检查点目录。这些主要是可恢复的会话状态。将它们与jsonl文件一起归档;不要留下孤立目录。<uuid>/ -
执行失败:通常是macOS上的
rmdir "$LOSER"或 stray的.DS_Store目录。重试前列出内容;不要 reflexively 使用todos/。rm -rf -
当前会话可能在待合并路径中。如果用户从非规范路径打开会话,当前文件正在写入待合并路径。不要在对话进行中归档该文件——正在运行的会话写入会失败。要么告知用户先执行
.jsonl,要么跳过当前会话的jsonl文件,归档其他内容。/exit -
跨项目名称冲突:如果两个项目名称共享一个标识(例如和
api),清单脚本会返回两者。始终向用户显示完整的候选列表,并在移动任何内容前确认。api-gateway -
不要试图判断“哪个会话记录更新”。这是内容合并问题,而非状态合并问题。本技能的任务是统一内存命名空间;活跃会话的记录保留在原位置。
Changelog
更新日志
V2 (2026-05-27)
V2(2026-05-27)
Optimized via . Honest note: external outcome research was thin — this is a procedural skill for one specific Claude Code mechanism, with no meaningful state-of-the-art to mine. The genuine outcome to protect is zero memory loss on merge, so the V2 change is a safety hardening, not a research import:
skillforge optimize- Reconciliation gate (Phase 6) — assert ; STOP on any mismatch. Turns "looks done" into "provably lost nothing." Pairs with the existing archive-never-delete rule (the merge is already reversible).
winner_after == winner_before + loser − dedup - No outcome-research-driven additions were forced (the agent-memory three-layer model is already reflected in the legacy-orphan variant's repo/local/secret routing).
通过优化。说明:外部成果研究有限——这是针对Claude Code特定机制的流程技能,没有可借鉴的先进技术。需要保护的核心成果是合并时零内存丢失,因此V2的变化是安全强化,而非研究引入:
skillforge optimize- 对账校验 gate(阶段6)——验证;出现任何不匹配时立即停止。将“看起来完成”变为“可证明无内容丢失”。与现有的“归档而非删除”规则配合使用(合并操作已具备可恢复性)。
winner_after == winner_before + loser − dedup - 未强制添加基于成果研究的功能(代理内存三层模型已体现在旧版孤立内存变体的代码库/本地/保密路由中)。
See also
另请参阅
- — sanitized end-to-end walkthrough of a dual-cwd merge.
references/merge-example.md - — the Phase 1 helper.
scripts/inventory.sh - The legacy-orphan variant recovers keepers into the repo (ADRs, docs, ideas backlog) for the repo-side migration; this skill handles the namespace cleanup + tombstone.
- — 双cwd合并的完整脱敏演练。
references/merge-example.md - — 阶段1的辅助脚本。
scripts/inventory.sh - 旧版孤立内存变体将保留内容恢复到代码库(ADR、文档、待办想法)以支持代码库迁移;本技能处理命名空间清理和标记。