The full upstream README, mirrored here for reference. Install config, tool schemas, adoption signals, and an original overview live on the Claude Handoff listing page.
Turn any Claude Code session — even a crashed one — into a clean handoff.md another AI can continue from. And give Claude Code permanent project memory, distilled from your own history.
That's it. Your latest session becomes handoff.md: the conversation without
the noise, the files that changed, the commands that ran — opening with
instructions to the receiving assistant, so you can paste it straight into
Gemini, GPT, claude.ai, or a fresh Claude Code session with zero extra
prompting.

Claude Code stores every session locally as JSONL
(~/.claude/projects/…/*.jsonl), full of tool calls, tool results, thinking
blocks and system reminders. Existing exporters dump all of that into
markdown. claude-handoff instead produces a handoff document — and,
since it can read your entire history, a project memory brief too.
curl and audit.--llm when you want a real summary. Claude, OpenAI or Gemini via
your own API key — or --llm claude-cli, which runs your locally-installed
Claude Code CLI on your existing Pro/Max plan: no API key at all.agent-*.jsonl), whose full transcripts stay behind
--include-sidechains.chf --brief distills a project's ENTIRE session
history into one living brief — what this is, where things stand,
decisions, fixes, conventions, and an ordered resume plan of open
threads, every claim citing its session; --install-brief-hook injects
it into every new Claude Code session, so Claude starts already knowing
the project.password=…) are redacted from every output — the handoff you paste into
a web chat is egress too. --anonymize goes further for public sharing.| Requirement | Minimum | Check | Notes |
|---|---|---|---|
| Python | 3.9+ | python3 --version | The only hard requirement |
| Claude Code | any | claude --version | Only for --llm claude-cli (uses your Pro/Max login) |
| pipx (recommended) | any | pipx --version | pip install pipx — or use brew / plain pip |
No third-party Python packages, ever — everything runs on the standard library.
Installing the package gives you two identical commands: claude-handoff
and the short alias chf. Tab completion:
A session crashed, hit the usage limit, or you closed the terminal:
…then paste into claude.ai, ChatGPT, Gemini — or a fresh claude session.
Works on any old session; nothing needed to be installed before the crash.
Moving work from Claude Code to another model:
"Which session was it where we talked about CORS?"
Give Claude Code permanent memory of this project:
A real summary instead of the transcript (goal / decisions / state / next):
A claude.ai or ChatGPT web chat instead of a terminal session:
--brief)Claude Code forgets everything between sessions — but the whole history is
on your disk. chf --brief reads every session of the current project
and writes one memory document to ~/.claude/briefs/<project>.md:
--llm, a distilled memory that opens with what this is
(product, stack, current state) and where things stand (done / in
flight / not started), then decisions with their why, fixed bugs,
conventions — and open threads as an ordered resume plan, each with
its concrete next action and a [in flight] / [blocked] /
[not started] tag. Every bullet cites the session id it came from
(chf --name <id> opens the source).
Per-session notes are cached, so refreshing after new sessions only pays for the new ones — and a monster session (beyond ~120k chars) is map-reduced inside the note, so the memory path never truncates: nothing is silently dropped, at any size.
You can also curate what feeds the memory: --exclude <id> leaves a
session out (a duplicate, an experiment — bare --exclude opens a
numbered picker that remembers: the stored exclusions arrive
pre-selected with ✗ and typed numbers toggle, so you edit the set
instead of re-picking it), and --keep first:2,last:20 windows a huge history to
the founding sessions plus a sliding recent window. Both are sticky —
stored in the brief's stamp, so hooks and later refreshes keep honoring
them until you change them (--exclude none, --keep all). And
chf --brief -o clipboard ships the current brief — distillation
included — straight to the clipboard, one paste away from handing your
project memory to another model.
The brief also goes beyond the project store: chf --brief --grep X -o - distills a thematic memory (only the sessions that talked
about X), and chf conversations.json --brief -o brief.md builds
standing memory from a claude.ai or ChatGPT export — every
conversation, cited by its id. Both are exports by design (explicit
-o only) so they never overwrite the standing brief.
Running graphify? The two tools compose in both directions, zero
config: chf --brief -o graphify files the current memory into raw/
— one evolving project-memory.md that the next /graphify --update
links into the code's knowledge graph — and once
graphify-out/graph.json exists, every brief gains a free ## Code map (communities, hub concepts, cross-community bridges:, labeled
flows:) so new sessions know the code's structure, not just its
history. The in-project copies (raw/project-memory.md, optional root
BRIEF.md) refresh on every brief write — strictly refresh-only,
hooks never create files. The full loop lives in
docs/GUIDE.md.
installs two hooks: SessionStart injects the brief as context (Claude
starts already knowing your project — re-injected after /compact too),
SessionEnd auto-refreshes the factual part for free. No LLM ever runs
from a hook; the distilled part refreshes only when you say so. The brief
carries a freshness stamp, and when newer sessions or commits exist the
file and the injection don't just warn — they list them (session titles,
commit subjects), so a fresh session sees what changed, not merely that
something did. Fully local; redaction applies as everywhere.
→ Step-by-step mechanics, the honest cost table, and a full day-with-it walkthrough: docs/GUIDE.md.
PreCompact matters: right before Claude Code compacts a long session's context, both hooks snapshot state — the handoff preserves detail that compaction is about to squeeze away, and the brief skeleton stays fresh mid-session.
Both edit ~/.claude/settings.json non-destructively, are idempotent, and
have matching --uninstall-* flags. Hook failures never break the host
session, and hooks never trigger LLM calls or create files on their own.
The third one is for Claude itself: --install-skill puts a
/claude-handoff skill into ~/.claude/skills/ (plus its trigger in
~/.claude/CLAUDE.md), so Claude Code knows the tool's grammar instead of
guessing — which session bare chf picks from inside a live session,
claude-cli (subscription) vs claude (API key), --fit for token
budgets, no interactive pickers from an agent shell. Idempotent, respects
a hand-written section, and --uninstall-skill removes both pieces
without touching anything else in either file.
Every turn is there, condensed to its lead — it's a summarize tool, not
an exporter. --full switches to classic verbatim messages, and --llm
replaces the digest with a real summary that read everything.
Where does it look? Sessions live in Claude Code's global store
(~/.claude/projects), so you can run chf from anywhere. If your current
directory is a project (or a subfolder of one), it scopes to that
project's sessions; a parent "master folder" scopes to every project under
it; --any ignores the directory entirely. Auto-selection skips
nearly-empty sessions (like the stub claude /login leaves behind) so
"latest" means your latest real conversation — an explicit path, --name
or -i always wins.
Big sessions. Transcripts beyond one pass (~400k chars) are summarized
map-reduce style: notes per chunk, then one synthesis — nothing is silently
dropped, and finished chunks are cached in ~/.cache/claude-handoff so an
interrupted run resumes for free. Chunks run 4-way parallel on API
providers; claude-cli and ollama stay sequential by design. In a
terminal you get a live progress bar:
Sessions with API usage data also get a Tokens line in the header,
and every run reports the output's ≈token size.
--llm — deterministic mode
is fully offline.password=…) are stripped from the
handoff itself, hook files, and MCP replies — a pasted document is egress
too. --no-redact opts out per run (and is deliberately not allowed in
the config file).--anonymize additionally collapses your home directory to ~ and
replaces emails, IPv4s and your username with placeholders — for pasting
into public issues and forums.--llm claude-cli and --llm ollama keep everything inside accounts and
machines you already control.Put defaults you always use in ~/.config/claude-handoff/config.json
(CLI flags always win; CLAUDE_HANDOFF_CONFIG overrides the path):
Allowed keys: llm, model, fit, output, include_tools,
include_sidechains, max_chars, anonymize, focus. Security switches
(no_redact) are deliberately not configurable — weakening redaction
must be an explicit per-run choice. A broken config warns and is ignored,
never fatal.
| Variable | Purpose |
|---|---|
ANTHROPIC_API_KEY / CLAUDE_API | key for --llm claude (first set wins) |
OPENAI_API_KEY / GPT_API | key for --llm openai |
GEMINI_API_KEY / GOOGLE_API_KEY / GEMINI_API | key for --llm gemini |
OLLAMA_MODEL / OLLAMA_BASE_URL | local Ollama model and endpoint |
CLAUDE_HOME | Claude Code home (default ~/.claude) — where sessions, handoffs and briefs live |
CLAUDE_HANDOFF_CACHE | chunk/note cache dir (default ~/.cache/claude-handoff) |
CLAUDE_HANDOFF_CONFIG | config file path (default ~/.config/claude-handoff/config.json) |
CLAUDE_HANDOFF_DEBUG | 1 = same as --debug; also lights up the hooks (add it to the hook command or your shell env) |
claude-cli needs no variable — it shells out to your installed
Claude Code CLI, billed to your Pro/Max plan
(run claude once to log in).
Any MCP client (Claude Desktop, Claude Code, …) can pull handoffs directly:
Tools: list_sessions (what's on this machine) and handoff (build the
document for a session by name/project/path; pass anonymize for a
shareable version). Deterministic by default — an MCP client can only
trigger LLM summaries when you start the server with --allow-llm.
claude-handoff: command not found after pip install
pip puts scripts in a user bin dir that may not be on PATH. Use
pipx install claude-handoff or brew — both manage PATH — or add
~/.local/bin (Linux) / ~/Library/Python/3.x/bin (macOS) to your PATH.
"No sessions found under ~/.claude/projects"
You're on a machine (or user) that hasn't run Claude Code, or your store
lives elsewhere — point CLAUDE_HOME at it. Inside a project folder the
tool scopes to that project; pass --any to search everything.
It picked the wrong session
"Latest" skips nearly-empty stubs but is still just the newest file. Use
-i (picker), --name "part of the title", or --grep "something said".
--llm claude-cli fails or asks to authenticate
Run claude once and log in (/login). It works even when invoked from
inside a Claude Code session — inherited CLAUDE* env vars are scrubbed
so the nested CLI authenticates like a fresh one.
--llm claude-cli fails with "Credit balance is too low"
An ANTHROPIC_API_KEY exported in your shell was winning over your
Pro/Max login and billing an (empty) Console account. Since 0.18.0 the
key is scrubbed from the spawned CLI automatically — upgrade if you see
this on an older version, or unset ANTHROPIC_API_KEY for the run.
"Set ANTHROPIC_API_KEY … to use --llm claude"
API providers need a key in the environment — see the table above. No key
at all? Use --llm claude-cli (subscription) or --llm ollama (local).
--fit refuses to combine with --llm / --max-chars
--fit sizes the deterministic output on its own. If you didn't type it,
your config file probably sets fit — override with an explicit
--max-chars removed, or drop the key.
The brief injection warns "sessions newer than this brief exist"
That's the freshness stamp doing its job: run
chf --brief --llm claude-cli to re-distill (cached — only new sessions
are paid for). The factual part refreshes itself if the SessionEnd hook is
installed.
Something silently did nothing?
Tolerant-by-design paths (corrupt JSONL lines, unreadable files, cache
trouble) never crash the run — add --debug (or CLAUDE_HANDOFF_DEBUG=1)
to see exactly what was skipped and why. Hooks always report their
errors on stderr while still exiting 0.
Garbled characters on Windows
Set PYTHONUTF8=1 (the CI runs the whole suite that way).
| Flag | Meaning |
|---|---|
--list | list sessions (date, size, project, title · first prompt); with a conversations.json, list its chats |
--name QUERY | pick newest session (or web conversation) whose title/first prompt contains QUERY |
--grep TEXT | pick newest session whose conversation contains TEXT (repeat the flag to require ALL terms); with --list/-i shows every match with a 🔍 preview |
--project NAME | pick latest session whose project path contains NAME (repeatable — several projects together) |
-i / --interactive | pick session(s) from a numbered list — 1,3 or 2-4 merges several into one handoff |
--any | ignore the current directory; consider every project's sessions |
--last N / --since 2h | keep only the tail of the conversation (N user turns / a time window) |
--merge | merge every session in scope into ONE handoff (session-break markers, summed activity) |
--brief | distill the project's whole history into ~/.claude/briefs/<project>.md (deterministic; --llm for real distillation) |
--exclude ID | with --brief: leave session(s) out of the memory — an id prefix (from --list or the brief's citations), comma-separate or repeat for several; bare --exclude opens a numbered picker with the stored set pre-selected (✗) — numbers toggle, empty keeps, none clears; sticky across refreshes, --exclude none clears |
--keep SPEC | with --brief: window the sessions that feed the memory — 20 / last:20 (most recent), first:2 (founding), since:7d (by last activity; ISO dates work too), or combinations like first:2,since:30d; sticky, so refreshes keep a sliding window; --keep all clears |
--install-brief-hook / --uninstall-brief-hook | project memory hooks: inject the brief at SessionStart, auto-refresh facts at SessionEnd |
--install-hook / --uninstall-hook | auto-write a handoff to ~/.claude/handoffs/ when each session ends |
--install-skill / --uninstall-skill | install the /claude-handoff Claude Code skill (SKILL.md under ~/.claude/skills/ + trigger section in ~/.claude/CLAUDE.md) so Claude drives chf correctly — idempotent, hand-written sections respected, uninstall leaves the rest of CLAUDE.md untouched |
--format md|json | markdown (default) or machine-readable JSON — also applies to --list |
-o FILE / -o - / -o clipboard / -o graphify | output file / stdout / clipboard / graphify corpus (raw/, for the knowledge graph) (default handoff.md) |
--fit TOKENS | size the deterministic handoff to a token budget (32k, 128k, 1m) by tightening transcript truncation |
--full | verbatim conversation turns (classic transcript) instead of the default condensed digest |
--max-chars N | cap the transcript section (default 80 000; keeps start + recent end) |
--include-tools | collapsed <details> blocks with each tool call |
--include-sidechains | append full subagent transcripts (inline sidechains and <session-id>/subagents/agent-*.jsonl); their file/command activity is always counted |
--llm claude|openai|gemini|claude-cli|ollama | LLM summary instead of raw cleaned transcript |
--model ID | override the LLM model |
--focus TEXT | extra instructions for the summary (e.g. --focus "emphasize the API decisions") |
--with-transcript | with --llm, also append the cleaned transcript |
--anonymize | strip identity for public sharing: home paths → ~, emails/IPs/username → placeholders |
--no-redact | keep secret-looking strings (default: redacted from every output, LLM or not) |
--no-cache | disable the chunk-note cache (~/.cache/claude-handoff) |
--mcp | run as an MCP server over stdio |
--allow-llm | with --mcp: let the handoff tool run LLM summaries (explicit opt-in) |
--completions bash|zsh | print a tab-completion snippet |
--debug | report tolerated failures (corrupt lines, unreadable files) on stderr — nothing becomes fatal |
/compact-continued sessions and offer to merge the lineage (--follow)PRs welcome.
This space isn't empty — it's fragmented. Pick the tool that matches your situation:
npm i -g continues) reads 16 coding CLIs' native session stores (Claude Code included) and injects a context doc into another terminal tool. Excellent for Claude Code → Codex/Cursor/Gemini CLI; but it can't target web chats, does no LLM summarization, and needs Node 22.5+.claude-handoff is the post-hoc, paste-anywhere corner of this map: it works on the JSONL after the fact — old sessions, crashed sessions, sessions that hit the usage limit — needs nothing installed in advance, costs zero tokens by default, can write a real summary when you ask for one (--llm), and produces a document any receiving model can pick up, including claude.ai, ChatGPT and Gemini in the browser or on your phone. And with --brief, it's the only one that turns that history into standing project memory.
Runtime code lives in the claude_handoff/ package; single/claude_handoff.py
is generated — rebuild it with python3 scripts/build_single.py after
any package change (CI fails when it's stale). New parser behavior starts
with a redacted fixture in tests/fixtures/ — see
CONTRIBUTING.md and AGENTS.md (instructions
and invariants for both human and AI contributors).
docs/GUIDE.md — a day with claude-handoff: walkthrough, how --brief works step by step, honest cost table, cheatsheet · INDEX.md — file map · docs/DEVELOPMENT.md — architecture, JSONL schema notes, design decisions · AGENTS.md — contributor guide for AI coding agents · CONTRIBUTING.md · CHANGELOG.md
MIT
mcp-name: io.github.Vasilispapg/claude-handoff