session-handoff · git:20261029.701fb99 · 2026-10-29 · sha256 14e542f4c5e7a61b

session-handoff git:20261029.701fb99A

Immutable. This exact content never changes and is served at /api/v1/blob/14e542f4c5e7a61b.

---
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` 不存在说明本机没用过该工具,脚本会跳过。