reunite
sessions 解决的是这一幕:你用另一个账号登录,侧边栏突然只剩下寥寥几条。什么都没被删。Claude 桌面端会为每一个登录过的账号单独存一份对话索引,而它只显示当前登录那一份。这个技能把这些索引取并集,于是无论你用哪个账号,看到的都是全部历史。
都是可选的。
- [--apply]
- [--into=all|current|<accountUuid>]
- [--no-titles]
- [--undo]
这些是 CI 用来评测这个技能的 prompt,每次发布都会跑。
会触发
- I signed in with my other Claude account and now the sidebar is almost empty. Where did all my conversations go?
- 换了个账号登录,session 全没了
- Can my two accounts on this machine share the same conversation list?
- I renamed a bunch of conversations under one account but my other account still shows the old titles.
不触发
- Rename these conversations onto the dated scheme.
- Move my Claude history over to my new laptop.
什么都没丢,而且可以自己验证
一次对话落在两个地方,其中只有一个知道账号这回事。
对话正文 —— 每一条消息、每一次工具调用 —— 是 ~/.claude/projects/ 下的一个 JSONL 文件,按你当时所在的目录归档。打开来看,里面从头到尾没有账号字段。所以不管当前登录的是哪个账号,终端里的 claude --resume 一直都列得出你的全部对话。
侧边栏读的是另一组小得多的文件:每条对话一份索引,存放路径以账号标识开头。换一个账号登录,app 就去读另一个目录。对话本身还在硬盘上,完整、未动。
所以要修的东西比症状小得多。没有什么需要恢复,需要合并的是那几份列表。
它做什么
把每个账号的索引条目,复制进其它每一个账号的索引。
Session index ~/Library/Application Support/Claude/claude-code-sessions
account bc95701b… 36 conversations lands in ee9e5ec5…
account cb24d9c9… 358 conversations lands in c04cc789…
account d58bde8d… 81 conversations lands in 2618534f… <- signed in
Plan: 720 entries to copy into 3 account index(es), +114.3MB
skipped 224 whose transcript is gone默认跑一次就到这份报告为止。不加 --apply 它一个字节都不写 —— 114MB、三个目录,这种事没把数字摆出来之前,不该替人做决定。
改名,以及为什么跑一次不够
改名只写一个索引文件。所以一条对话同时存在于三份索引之后,在某一个账号里改了名,另外两份还留着旧名字——而复制修不了这个,因为那条记录本来就已经在了。
于是每次运行还会对齐标题。同一条对话的几份副本名字不一致时,最后被写入的那一份胜出,它的名字写进其余几份。只有标题会动,文件里其它内容都是那个账号自己的对话记录,原样不动。
报告会把每一条即将改动的都列出来,因为这个判据并不万无一失——文件 mtime 是唯一一个真正会被改名推动的时间戳,而一次无关的重写也能让过期的那份变成最新。--no-titles 可以整个关掉这一步。
这一步才让「定期改名」这件事真正成立:在一个账号里改完名,跑一次这个,名字就到了其它账号。
它拒绝做什么
从不删除。 每次运行只添加文件,并且把添加过的每一个都记下来。--undo 只删这些路径 —— 不碰 app 自己写的文件,也不碰上一次合并已经对齐的条目。
跳过正文已经不在的条目。 索引条目有可能比对话活得久。把它复制到处都是,结果就是侧边栏多出一行、点开却是空的,这比它不出现更糟。报告里会数出来,除非你明确要,否则留在原地。
可以反复跑。 新对话只会落进你当时登录那个账号的索引,所以这是一件要复跑的事,不是一次性的。第二次跑如果无事可做,它会规划零次复制并直说。
关于重启
合并要等桌面端重启才看得见。app 只在启动时读这份索引,运行期间不会再看那个目录。
这一点比听起来重要,因为重启会打断还在跑的对话。这个技能只告诉你需要重启,不替你动手;先花十秒看一眼哪些还活着,是值得的。
交给 agent 执行的完整指令
完整的 SKILL.md——这个技能触发时 Claude Code 读到的内容。上面是解释,这里是原件。
# Reunite
Union the desktop app's per-account conversation indexes, so whichever account is signed in sees the whole history.
## What is actually lost
Nothing. Establish that before offering to fix anything, because the fix is much smaller than the symptom suggests.
Two stores hold a conversation, and only one of them knows about accounts:
| Store | Path | Account-aware |
|---|---|---|
| Transcript — the conversation itself | `~/.claude/projects/<slugged-cwd>/<cliSessionId>.jsonl` | **No.** The JSONL carries `cwd`, `sessionId`, `version`, `gitBranch` and no account field at all. |
| Index — what the sidebar lists | `~/Library/Application Support/Claude/claude-code-sessions/<accountUuid>/<orgUuid>/local_*.json` | **Yes.** Identity is the directory path; nothing inside the file names an account. |
So switching accounts hides conversations from the sidebar and deletes none of them. `claude --resume` in a terminal reads the transcript store directly and has been listing all of them the whole time — say so, because it is the answer for anyone who only needs to reach one old conversation.
## Run it
```bash
python3 scripts/merge.py # report only — what would be copied, and how much disk
python3 scripts/merge.py --apply # write
python3 scripts/merge.py --undo # remove exactly what --apply wrote
```
Report first, always. The report names each account index, its conversation count, and the org subdirectory a copy would land in. Read it out before writing: `--apply` on three accounts moved 114MB here, and someone who has not seen the number has not agreed to it.
`--into` narrows what receives the union — `all` (default), `current` for just the signed-in account, or a specific `accountUuid`. Narrow it when one of the accounts is long dead and does not deserve a copy of everything.
**"Signed in" means the desktop app, not the CLI.** They hold separate logins and are routinely on different accounts, so `~/.claude.json` answers a different question — it names the account `claude` authenticates as, not the one whose sidebar is on screen. The app records its own as `lastKnownAccountUuid` in `config.json` beside the index, and that is the index a rename actually lands in. Check it before concluding a rename did not work; it may have worked in the other account.
## Titles drift after a union, and a second run is where that shows
A rename writes one index file. So once a conversation exists in three indexes, renaming it under one account leaves the other two holding the old name, and copying cannot fix it — the entry is already there, so a plain union skips it.
Each run therefore also reconciles titles: where copies of one conversation disagree, the most recently written file wins, and its `title`, `titleSource` and `previousTitles` are written into the others. Nothing else in those files moves; the rest is that account's own record of the conversation.
File mtime is the signal because it is the only timestamp a rename actually moves — `lastActivityAt` records the conversation, not the record of it. That is not infallible: an unrelated rewrite of a stale copy makes it the newest, and it then wins with the older name. So every reconciliation is listed by name in the report, the value it replaced is recorded, and `--no-titles` turns the whole pass off.
This is what makes a naming sweep worth running. Rename under one account, run this, and the names reach the others.
## What the script refuses to do
- **It never deletes.** Every run only adds files, and records both what it added and every title it replaced in `.session-merge-manifest.json` beside the account directories. `--undo` removes exactly those paths and puts exactly those titles back — not files the app wrote, not titles this skill never touched. A manifest written before titles were reconciled is a bare list of paths and is still read as one, so an older merge stays undoable.
- **It skips orphans.** An index entry whose `cliSessionId` has no transcript under `~/.claude/projects/` would appear in the sidebar and open to nothing, which is worse than not appearing. The report counts them; `--keep-orphans` copies them anyway.
- **It is idempotent.** A second run with nothing new plans zero copies and zero reconciliations. Run it again after every stretch of work under one account, and after any renaming pass: new conversations only land in that account's index, and new names only land in that account's copy.
## The restart
**A merge does not show up until the desktop app restarts.** The app reads this index at startup and does not rescan the directory while running — verified by writing an entry with a current timestamp and watching it stay invisible to a running app.
So the last line of any report is the restart, and it is worth naming what the restart costs: running conversations are interrupted. Check what is live first — `list_sessions` on the session-management MCP shows which ones are still running — and let the user pick the moment.
## Reporting
```
Session index <root>
account <uuid> <N> conversations lands in <orgUuid> <- signed in
...
Plan: <N> entries to copy into <M> account index(es), +<size>
skipped <N> whose transcript is gone
<N> stale titles to reconcile
<account> → <winning title>
```
After `--apply`, say how many were copied and how many titles were reconciled, that `--undo` takes both back, and that the sidebar is unchanged until the app restarts. A merge reported as done while the sidebar still looks the same reads as a failure.
## Platform
The paths above are macOS. `CLAUDE_DESKTOP_SESSIONS_DIR` overrides the index root; the script exits with that hint rather than guessing when the directory is not there.开发 里的其它技能
- sync/dev:syncsync 只做一件事:把本地和远端的差异摊开给你看,并且只执行其中绝对安全的那部分。
- ship/dev:shipship 把一次改动从工作区送到一个已合并的 PR:开分支、跑项目自己的测试、提交、开 PR、等 CI、合并,最后清理 worktree。
- cleanup/dev:cleanup一次改动发布之后,残留会留下来:本地分支、它在远端的副本、当时用的 worktree,有时还有一个只装着被忽略文件的目录。
- retitle/dev:retitle对话列表乱,不是因为名字不好听,是因为名字里没有可排序的信息。
- steward/dev:stewardsync、ship、cleanup 都只管你当下所在的那一个仓库。
- handoff/dev:handoffhandoff 让你在一个 agent 里正进行的对话,实时出现在另一个 agent 的历史里。