---
name: session-handoff
description: |
  跨 session 续接任务，Claude Code 与 Codex 通用。save：把目标、状态、用户裁决原话、下一步、关键路径、验证情况写成交接文件，按仓库身份（规范化的 git origin）存放，目录改名后仍找得到，同一任务覆盖更新；resume：列出当前仓库与全局最近的交接文件，读选中那份、复述后接着干；find：在 Claude Code 与 Codex 的会话记录里按关键词、旧目录名或仓库 origin 找回 session，给出恢复命令。用户说 save、存一下、交接、续接、接着上次、上次做到哪、找回 session、上个 session 在干啥、resume 不回来、我之前说过 时使用。
trigger: /session-handoff
compatibility: Claude Code, Codex
license: MIT
---

# session-handoff

会话会断（上下文用完、换工具、换目录），任务不能断。交接文件存在 `~/.claude/handoffs/<仓库键>/<任务名>.md`，Claude Code 和 Codex 读写同一处。

- **仓库键**：有 git origin 就用规范化后的 origin（如 `gitlab.example.com/group/repo`，协议、用户名、端口、`.git` 都去掉），所以目录改名、换 worktree 都还是同一个键；没有 origin 用 `local/<主检出目录名>`。
- 脚本只给路径、只读会话记录；交接文件的内容由你来写。

脚本：`scripts/handoff.py`（本 SKILL.md 所在目录下，只用 Python 3 标准库）。下文的 `HANDOFF` 指 `python3 <本 skill 目录>/scripts/handoff.py`。

| 用户说 | 做什么 |
|---|---|
| save、存一下、交接 | [save](#save) |
| 接着上次、续接、上次做到哪 | [resume](#resume) |
| 找回 session、上个 session 在干啥、resume 不回来、我之前说过（但没有交接文件） | [find](#find) |

## save

1. 在任务目录里运行 `HANDOFF path [--task <任务名>]`。在任务分支上可省略 `--task`，脚本取分支名（去掉 `task/` 等前缀）；在 main/master 上必须给任务名，用短而稳定的名字，下次续写用同一个。
2. 输出「已存在：是」时，先读旧文件再在它上面更新：保留全部旧裁决，状态、下一步、路径改成最新。同一任务只有一份文件，不另建。
3. 用下面的模板写入。只写这六节；不贴对话流水和大段日志；已在 PR、文档、提交里的内容给路径或链接。

```markdown
# <任务名>

## 目标
<一句话>

## 状态
完成度 <X%>（口径：<按什么算>）。<停在哪一步，卡在什么>

## 用户裁决
- <YYYY-MM-DD>「<用户原话>」——<约束了什么>

## 下一步
1. <可直接执行的步骤：命令、文件、要改的点>

## 关键路径
- 仓库：<origin> · worktree：<路径> · 分支：<名>
- PR/MR：<链接>
- 产物：<路径>

## 验证
- 已验证：<命令 → 结果>
- 未验证：<项 → 怎么验>
```

4. 回报文件路径，并告诉用户下次怎么续：在新 session（Claude Code 或 Codex 都行）里说「接着上次」。

### 用户裁决当场落盘

本 session 里用过本 skill（save、resume 过）之后，用户每给出一次裁决（选定方案、否掉某个做法、定下口径、「以后都这样」），立即更新当前任务交接文件的「用户裁决」节：原话引用，标日期，不等 session 结束。新裁决推翻旧裁决时，旧条目保留并注明「已被 <日期> 的裁决推翻」，不静默删除。新 session 动手前先读裁决，不做与之相反的事。

## resume

1. 运行 `HANDOFF list`。输出两组：当前仓库的交接文件、全局最近更新的几份（`--limit N` 调整），各组按修改时间倒序，附每份的「目标」一句。
2. 只有一份对得上用户说的任务就直接读；有多份就列出任务名和目标让用户选。都对不上，转 [find](#find)。
3. 读选中的文件，先用三行复述：目标、状态、下一步，再列出与下一步相关的用户裁决。然后按「下一步」接着干，除非这一步本身需要用户拍板。
4. 文件里的路径已不存在（目录改名、worktree 回收）时，按仓库键找当前检出位置继续，下次 save 时更新「关键路径」。

## find

用户找不到某个 session，或者要找的东西不在任何交接文件里时用。

1. 运行 `HANDOFF find [关键词 ...] [--days N] [--limit N]`。
   - 关键词全部命中才算匹配，匹配范围：会话的工作目录、仓库 origin、用户消息原文；不区分大小写。
   - 不给关键词就是列出最近的会话（回答「我最近一个 session 在干啥」）。
   - 默认只看最近 30 天有活动的记录，搜不到再加 `--days 0` 搜全部历史。
   - 用户说旧目录名时，用路径片段当关键词（如 `code/data`）；命中太多就再加一个话题词缩小。
2. 每个匹配输出：工具、session id、工作目录（标注已不存在）、仓库、起止时间、首条和末条用户消息摘要、恢复命令。Codex 的子 agent 线程不单独列出，恢复父会话即可。
3. 把候选交给用户，按首末条消息判断是哪一个。需要更多上下文时，只抽该记录文件里的用户消息，不要把整个文件读进来。
4. 恢复命令由用户在新终端里运行，当前 session 不能切换成另一个 session：
   - **Claude Code**：`cd <原目录> && claude --resume <id>`。原目录不存在时，脚本会给出该会话后来到过、同一仓库的现存目录；没有就请用户 cd 到改名后的新目录再运行。`--resume <id>` 按 id 跨所有项目目录查找，不需要复制记录文件（Claude Code 2.1.312 实测）；不带 id 的 `claude --resume` 选择器和 `--continue` 只列当前目录的会话，这就是改名后「resume 不回来」的原因。
   - **Codex**：`codex resume <id>`。当前目录与会话记录的目录不同时，Codex 会问用哪个；原目录不存在就选 Use current directory。
5. 不想恢复旧会话时，在当前 session 里读出要点，save 一份交接文件接着干。找回之后都建议 save 一份，下次不用再找。

## 隐私与边界

- 脚本只读会话记录，不改、不删、不复制。消息摘要截断到 80 字，疑似密钥的长串替换为 `***`；不要把会话记录原文大段贴给用户。
- 不另建记忆系统：用户裁决只记在对应任务的交接文件里。

## 错误处理

- `path` 报「当前不在任务分支上」：加 `--task <任务名>`。
- 不在 git 仓库或没有 origin：仓库键退化为 `local/<目录名>`，目录改名后键会变；`list` 的全局区仍能看到旧文件。
- `find` 没结果：换关键词，或加 `--days 0`。`~/.claude/projects` 或 `~/.codex/sessions` 不存在说明本机没用过该工具，脚本会跳过。
