---
name: session-handoff
description: Project-AGNOSTIC transfer of full session knowledge between machines/Claude Code instances (v3 — works for ANY project, self-activating, self-updating). AUTO-UPDATE + AUTO-SETUP run first on every invocation and right after install or re-install - they bring every installed copy of the skill up to the latest version, install the post-commit refresh hook and per-machine git config in the current repo and creates the handoff bundle if the repo has none, so nothing needs manual setup. EXPORT auto-detects the active repo, this project's Claude memories, the active plan, and the live task backlog, then writes a fresh handoff bundle into the repo and commits it (travels via git). IMPORT rehydrates a fresh instance on another machine. TRANSLATE renders the narrative into a requested human language. Also the after-every-commit auto-refresh rule. Invoke on "install the session-handoff skill", "save/transfer/hand off the session", "import the session on this machine", "translate the handoff", or after a commit to refresh.
---
<!-- skill-version: 3 -->

# session-handoff (v3) — portable, project-agnostic, self-activating session knowledge

Captures whatever session is ACTIVE. Nothing here is hardcoded to a specific project — you auto-detect the current one and generate the bundle from the live state.

## Auto-detect (run first, every mode)
- **Repo root:** `git rev-parse --show-toplevel`. Bundle dir = `<root>/handoff/` (use `<root>/docs/handoff/` if a `docs/` dir already exists). If `git config handoff.bundle` is already set, use that.
- **Claude home:** Windows Git-Bash `"$USERPROFILE/.claude"`; macOS/Linux `"$HOME/.claude"`.
- **This project's memory dir:** `<CLAUDE_HOME>/projects/<slug>/memory/` — `<slug>` is Claude's path-derived name for the repo's working dir: the absolute path with every non-alphanumeric character replaced by `-` (e.g. `D:\WORK` → `D--WORK`, `/home/me/app` → `-home-me-app`). If that dir doesn't exist, `ls "<CLAUDE_HOME>/projects/"` and match, or ask the user.
- **Active plan(s):** newest of `<CLAUDE_HOME>/plans/*.md` (or the plan the session references). May not exist — then skip `plan.md`.
- **Live task backlog:** your task tool (list every task + status). If no task tool is available, list the session's tasks from the conversation.

## AUTO-UPDATE + AUTO-SETUP (run FIRST on every invocation, and immediately after this skill is installed or re-installed)
Idempotent — safe to re-run any number of times. The goal: whoever installs, re-installs or starts this skill ends up on the **latest version** with a fully working setup in the current repo, without any manual step.

0. **AUTO-UPDATE.** Run the update script below as `sh update.sh session-handoff` (write it to a temp file in your scratchpad). It brings every existing copy of this skill — global install, project install, the repo's tracked `docs/handoff/`/`handoff/` mirror, a root-level `session-handoff-skill.md` — up to the highest `skill-version` among them. It never downgrades and never creates copies in new places.
   - If it reports a newer version than the file you are following, **re-read that newer `SKILL.md` and follow it instead** from here on.
   - Commit any updated tracked mirror with explicit paths.
   - Installing over an existing copy: never replace a higher `skill-version` with a lower one. If the text you were given is older than what's installed, keep the installed one and say so.
1. **Not inside a git repo?** Nothing to activate. Report "session-handoff is installed; it activates automatically the first time it runs inside a git repo" and stop (unless the user asked for something else).
2. **Run the setup script below** (write it to a temp file in your scratchpad and `sh` it from the repo root; pass the bundle dir as `$1` only to override auto-detection). It installs/refreshes the `post-commit` hook — always rewriting it, so an older hook is upgraded to this version's — keeps any existing hooks working, pins the hook to LF line endings, and sets the per-machine `handoff.*` git config.
3. **No `<bundle>/SESSION_HANDOFF.md` yet?** Run **EXPORT** now so the hook has a bundle to refresh. If it already exists, don't regenerate it just because AUTO-SETUP ran — only do what the user asked (EXPORT again, IMPORT, TRANSLATE, or a narrative refresh).
4. **Commit** what setup created, with explicit paths: `git commit -- .gitattributes <hooks-dir>/post-commit <bundle>/...` — never a bare `git commit`/`git add -A` (other staged work must not be swept in).
5. **Report** the update result, the setup script's one-line summary (hooks dir, bundle, memory dir) and what EXPORT wrote.

```sh
#!/bin/sh
# skill AUTO-UPDATE (shared by session-handoff, achievements-system, resilient-agent-handoff) - idempotent.
# Usage: sh update.sh <skill-name>. Brings every EXISTING copy of the skill on this machine/repo up to
# the highest skill-version found among them. Never downgrades, never creates copies in new places.
name=${1:?usage: update.sh <skill-name>}
if [ -n "${USERPROFILE:-}" ]; then home="$(printf '%s' "$USERPROFILE" | sed 's|\\|/|g')/.claude"; else home="$HOME/.claude"; fi
root=$(git rev-parse --show-toplevel 2>/dev/null || true)
ver() { v=$(tr -d '\r' < "$1" 2>/dev/null | sed -n 's/^<!-- skill-version: *\([0-9][0-9]*\) *-->$/\1/p' | head -1); echo "${v:-0}"; }
tmp=$(mktemp)
printf '%s\n' "$home/skills/$name/SKILL.md" > "$tmp"
if [ -n "$root" ]; then
  printf '%s\n' "$root/.claude/skills/$name/SKILL.md" "$root/docs/handoff/$name-skill.md" \
    "$root/handoff/$name-skill.md" "$root/$name-skill.md" >> "$tmp"
fi
best=; bestv=-1
while IFS= read -r f; do
  [ -f "$f" ] || continue
  v=$(ver "$f")
  if [ "$v" -gt "$bestv" ]; then best=$f; bestv=$v; fi
done < "$tmp"
if [ -z "$best" ]; then rm -f "$tmp"; echo "$name: no installed copy found"; exit 0; fi
updated=0
while IFS= read -r f; do
  { [ -f "$f" ] && [ "$f" != "$best" ]; } || continue
  if [ "$(ver "$f")" -lt "$bestv" ]; then
    tr -d '\r' < "$best" > "$f.new" && mv -f "$f.new" "$f" && updated=$((updated + 1)) && echo "$name: updated $f -> v$bestv"
  fi
done < "$tmp"
rm -f "$tmp"
echo "$name: latest is v$bestv at $best ($updated older copies updated)"
```

```sh
#!/bin/sh
# session-handoff AUTO-SETUP (v3) — idempotent. Run from anywhere inside the repo: sh setup.sh [bundle-dir]
set -e
root=$(git rev-parse --show-toplevel 2>/dev/null) || { echo "session-handoff: not a git repo - nothing to activate"; exit 0; }
cd "$root"
bundle=${1:-$(git config --get handoff.bundle || { if [ -d docs ]; then echo docs/handoff; else echo handoff; fi; })}

# Claude home + this repo's memory dir (slug = absolute repo path, every non-alphanumeric char -> '-')
if [ -n "${USERPROFILE:-}" ]; then home="$(printf '%s' "$USERPROFILE" | sed 's|\\|/|g')/.claude"; else home="$HOME/.claude"; fi
abs=$(pwd -W 2>/dev/null || pwd)
slug=$(printf '%s' "$abs" | sed 's|[^A-Za-z0-9]|-|g')
memdir="$home/projects/$slug/memory"

# Hooks dir: respect an existing core.hooksPath (husky etc.); otherwise use .githooks and carry classic hooks over
hooks=$(git config --get core.hooksPath || true)
if [ -z "$hooks" ]; then
  hooks=.githooks
  mkdir -p "$hooks"
  classic=$(git rev-parse --git-path hooks)
  for h in "$classic"/*; do
    case "$h" in *.sample) continue ;; esac
    if [ -f "$h" ] && [ ! -e "$hooks/$(basename "$h")" ]; then cp "$h" "$hooks/"; fi
  done
  git config core.hooksPath "$hooks"
fi
mkdir -p "$hooks"

# Chain a foreign post-commit instead of overwriting it
if [ -f "$hooks/post-commit" ] && ! grep -q 'session-handoff' "$hooks/post-commit"; then
  mv "$hooks/post-commit" "$hooks/post-commit.local"
fi

cat > "$hooks/post-commit" <<'HOOK'
#!/bin/sh
# session-handoff (v3): refresh the bundle's mechanical parts after each commit, then commit them.
here=$(cd "$(dirname "$0")" && pwd)
if [ -x "$here/post-commit.local" ]; then "$here/post-commit.local" || true; fi
[ -n "${HANDOFF_SKIP:-}" ] && exit 0
root=$(git rev-parse --show-toplevel) || exit 0
cd "$root" || exit 0
# Never add commits in the middle of a rebase/merge/cherry-pick/revert, or on a detached HEAD
for s in rebase-merge rebase-apply MERGE_HEAD CHERRY_PICK_HEAD REVERT_HEAD; do
  [ -e "$(git rev-parse --git-path "$s")" ] && exit 0
done
git symbolic-ref -q HEAD >/dev/null || exit 0
bundle=$(git config --get handoff.bundle) || exit 0
[ -f "$bundle/SESSION_HANDOFF.md" ] || exit 0
# Loop guard: skip when the commit touched only the bundle (incl. this hook's own commit)
git diff-tree --root --no-commit-id --name-only -r HEAD | grep -v "^$bundle/" | grep -q . || exit 0
memdir=$(git config --get handoff.memorydir)
plan=$(git config --get handoff.plan)
if [ -n "$memdir" ] && [ -d "$memdir" ]; then
  mkdir -p "$bundle/memories" && cp -f "$memdir"/*.md "$bundle/memories/" 2>/dev/null
fi
if [ -n "$plan" ] && [ -f "$plan" ]; then cp -f "$plan" "$bundle/plan.md"; fi
stamp="**Generated:** $(date +%Y-%m-%d) · $(git rev-parse --abbrev-ref HEAD) · $(git rev-parse --short HEAD) · $(git remote get-url origin 2>/dev/null || echo no-remote)"
esc=$(printf '%s' "$stamp" | sed 's/[&|\\]/\\&/g')
sed -i.bak "s|^\*\*Generated:\*\*.*|$esc|" "$bundle/SESSION_HANDOFF.md" && rm -f "$bundle/SESSION_HANDOFF.md.bak"
git add -- "$bundle"
git diff --cached --quiet -- "$bundle" && exit 0
git commit -q -m "handoff: auto-refresh bundle" -- "$bundle"
HOOK
chmod +x "$hooks/post-commit"

# Pin hook scripts to LF, or core.autocrlf=true checkouts turn them CRLF and sh fails on '\r'
case "$hooks" in
  /*|?:*) ;;  # hooks dir outside the repo - nothing to pin
  *) line="$hooks/* text eol=lf"
     grep -qxF "$line" .gitattributes 2>/dev/null || printf '%s\n' "$line" >> .gitattributes ;;
esac

git config handoff.bundle "$bundle"
if [ -d "$memdir" ]; then git config handoff.memorydir "$memdir"; else echo "session-handoff: memory dir not found ($memdir) - set later: git config handoff.memorydir <path>"; fi
plan=$(ls -t "$home"/plans/*.md 2>/dev/null | head -1 || true)
if [ -n "$plan" ]; then git config handoff.plan "$plan"; fi
echo "session-handoff active: hooksPath=$hooks bundle=$bundle memorydir=$(git config --get handoff.memorydir || echo unset) plan=${plan:-none}"
```

## EXPORT (source machine — capture + bundle the CURRENT session)
1. `mkdir -p "<bundle>/memories"`.
2. Copy the live memories + plan into the bundle: `cp -f "<memory dir>"/*.md "<bundle>/memories/"` and `cp -f "<active plan>" "<bundle>/plan.md"` (skip the plan if none exists).
3. **Write `<bundle>/SESSION_HANDOFF.md` FRESH from THIS session** (do not copy another project's text). Include, generically:
   - Title + one-line purpose + a **Generated** stamp as its own line starting with `**Generated:**` (date · branch · `git rev-parse --short HEAD` · remote) — the hook rewrites that exact line.
   - **§1 How to use** (clone + checkout the branch, read this, import memories, paste the multiprompt).
   - **§2 What this project is** — a short factual description of THIS repo (infer from README/CLAUDE.md/the conversation).
   - **§3 Git state** — remote, working branch, latest commit; how to get the code.
   - **§4 Accomplishments this session** — summarize from `git log --oneline -20` (this session's commits) + what you did in the conversation.
   - **§5 In-flight / uncommitted** — running background agents/forks, uncommitted work, known-broken things.
   - **§6 Next actions** — where to continue.
   - **§7 Environment / cross-machine notes** — toolchains, SDKs, per-OS differences relevant to continuing (esp. Windows↔Mac↔Linux).
   - **§8 Memories** — list `memories/` + how to import (copy into the destination's `<CLAUDE_HOME>/projects/<slug>/memory/`, merge `MEMORY.md`).
   - **§9 Plan** — point to `plan.md` (or say there is no active plan).
   - **IMPORT MULTIPROMPT** at the bottom — a paste-ready block (see template below).
4. **Write `<bundle>/TASKS.md`** from your task tool (every task + status).
5. **Copy this skill into the bundle** so it travels (`.claude/` is usually gitignored): `cp -f "<CLAUDE_HOME>/skills/session-handoff/SKILL.md" "<bundle>/session-handoff-skill.md"` (or from a project-level `.claude/skills/session-handoff/SKILL.md`).
6. Make sure **AUTO-SETUP** has run in this repo (hook + git config) — it is idempotent.
7. Stage + commit ONLY the bundle (+ `.gitattributes` and the hooks dir if setup created them) with explicit paths, then push if the user wants it pushed.

### IMPORT MULTIPROMPT template (put at the bottom of SESSION_HANDOFF.md, filled for this project)
```
You are resuming the "<PROJECT>" project on a new machine. Do this:
(Paths below use handoff/ — write docs/handoff/ instead if that is this repo's bundle dir.)
1. Read handoff/SESSION_HANDOFF.md, handoff/TASKS.md, and handoff/plan.md (if present) in full.
2. Install the session-handoff skill: copy handoff/session-handoff-skill.md ->
   <CLAUDE_HOME>/skills/session-handoff/SKILL.md, then run its AUTO-SETUP (hook + git config for this machine).
3. Import the memories: copy handoff/memories/*.md into THIS machine's Claude memory dir for this
   repo (ask me for the path if unsure), merging MEMORY.md as the index. Honor them as standing rules.
4. Recreate the task backlog from handoff/TASKS.md with your task tool (statuses as listed).
5. Verify the toolchain per SESSION_HANDOFF §7 for THIS OS/hardware.
6. Summarize back: current git HEAD, top pending tasks, in-flight work; then continue from §6.
Do not re-do committed work — check `git log --oneline -20` first.
```

## IMPORT (destination machine — rehydrate ANY project)
1. `git clone <remote> && cd <repo> && git checkout <branch>` (branch from SESSION_HANDOFF §3).
2. Read `handoff/SESSION_HANDOFF.md`, `TASKS.md`, `plan.md`.
3. Install the skill from `handoff/session-handoff-skill.md` into `<CLAUDE_HOME>/skills/session-handoff/SKILL.md`.
4. Copy `handoff/memories/*.md` into THIS machine's `<CLAUDE_HOME>/projects/<slug>/memory/` for this repo (open the project once so the dir exists; merge `MEMORY.md`, don't clobber unrelated locals).
5. Run **AUTO-SETUP** — the hook file travels via git, but `core.hooksPath` and the `handoff.*` values are per-machine git config and must be set here. Recreate tasks from `TASKS.md`.
6. Continue from §6.

## TRANSLATE (optional — render the narrative in another language)
On "translate the handoff to <lang>": produce `handoff/SESSION_HANDOFF.<lang>.md` — translate ALL prose/headings into <lang> but **keep verbatim**: code, commands, file paths, git hashes, env-var names, identifiers. Add a top note "translation of SESSION_HANDOFF.md; code/commands unchanged". The English SESSION_HANDOFF.md stays the canonical source. (Useful for teammates in another language.)

## AUTO-UPDATE after every commit (standing rule)
In any repo that has a handoff bundle, after every successful commit refresh the bundle: narrative parts (§4/§5/§6 + TASKS statuses) you update yourself; the mechanical parts (Generated stamp + re-copy memories/plan) are handled by the `post-commit` hook that AUTO-SETUP installs. (Some projects also record this as the memory `session-handoff-update-rule`.)

How the hook behaves:
- The stamped hash is the commit that triggered the refresh; the follow-up `handoff: auto-refresh bundle` commit comes right after it.
- It skips commits that touch only the bundle (loop guard), rebases/merges/cherry-picks/reverts in progress, and detached HEADs.
- Set `HANDOFF_SKIP=1` for a single commit to bypass it; `git config --unset core.hooksPath` (or delete the hook) disables it.
- A project's pre-existing `post-commit` is preserved as `post-commit.local` and still runs first.

## Achievement history (permanent, separate from this rolling snapshot)
`SESSION_HANDOFF.md` is a rolling *current-state* snapshot (overwritten each refresh). Separately,
every substantive achievement gets a **permanent, timestamped, append-only** record in
`<bundle>/achievements/`. If the project's `CLAUDE.md` defines its own achievement-history rule,
follow that (the `achievements-system` skill sets one up); otherwise use this format:
- **File:** `<bundle>/achievements/YYYY-MM-DD_HHMMSS_<short-kebab-slug>.md` — one file per achievement,
  never edited after it is committed (a correction goes in a new file that links the old one).
- **Header:** title · timestamp · branch · commit hash(es).
- **Sections:** What was achieved · How it was done · Systems / algorithms involved · Findings ·
  Problems / errors hit and how they were resolved · Verification (tests, commands, output that proved it).

Write one after a real achievement lands (not every trivial edit); commit it. At the start of a
session, read the 3-5 most recent files there, alongside `SESSION_HANDOFF.md`.

## Notes
- Generic by design: the ONLY project-specific things are auto-detected (repo, memory dir, plan, tasks) or written fresh from the live session. Reuse this skill for any repo.
- The committed code is ground truth; the bundle is a snapshot. Always `git log --oneline -20` before redoing work.
- `<slug>` may differ per machine (path-derived) — that's fine; you copy memories INTO whatever dir the destination Claude uses for the repo.
