claude-plugins · handoff

Carry the thread across sessions.

handoff saves the directive of a wrap-up session, then surfaces it at the start of the next one. It replaces the manual "paste a prompt into the new window" workflow with an auto-save-with-confirm loop and a surface-and-confirm resume.

plugin session-continuity context-transfer session-start-hook
installbash
claude plugin install handoff@apurvbazari-plugins

That's it — nothing to configure. The SessionStart hook ships inside the plugin and activates automatically.

4
skills
1
SessionStart hook
4
resume options
1
active slot per repo
v1.0.0
folder layout
01 — what it does

Stop copy-pasting a prompt into the next window

Long Claude Code sessions often end with you typing a paragraph into the next window to continue. That paragraph is reproducible — it's just "the directive of where we left off". handoff captures it at end-of-session and surfaces it at the start of the next.

Saying a wrap-up phrase like "save handoff", "pick this up later", or "I'll come back to this" auto-invokes /handoff:save. It drafts a directive — what the next session should pick up, pointers worth reading first, constraints, and open questions — and confirms via an AskUserQuestion before writing to .claude/handoff/active.md. The directive is intent only; current commits, branch, and working-tree state are derivable at resume time, so they are deliberately not embedded.

The continuity loop

Save at the end of one session → the SessionStart hook surfaces it at the start of the next → you pick one of four ways to resume. Intent crosses the session boundary; nothing else has to.

02 — the loop

The save → resume pipeline

One save writes a single-slot directive. At the next session start, the bundled hook reads it, computes progress signals, and routes Claude to the pickup flow. Click a stage for detail.

/handoff:save
capture intent
write
active.md
next session
SessionStart hook
surface
additionalContext
/handoff:pickup
4-option resume

On every session start the hook reads .claude/handoff/active.md (exiting silently if absent), parses its frontmatter, and computes three progress signals: age in days, commits past saved-at-sha, and branch change. Two short-circuits run first — a snooze check (suppress re-surface for deferral-snooze-hours, default 24h, after a "Save for later") and a stale backstop (auto-archive anything older than stale-day-threshold, default 90 days). Otherwise it emits additionalContext and Claude invokes /handoff:pickup.

03 — resume

The four-option pickup

/handoff:pickup re-reads the file from disk (the surfaced content is treated as a stale snapshot), shows the directive plus progress, runs a cwd guard, then asks via AskUserQuestion. You stay in control — handoff never auto-executes a directive.

recommended when it still applies
option 1
Execute

Act on the directive in this session, then archive to archive/consumed-<ts>.md.

option 2
Edit

Open the directive in $EDITOR, revise, then re-surface for another confirm.

option 3
Discard

Archive to archive/discarded-<ts>.md without acting.

option 4
Save for later

Leave it in place; snooze for 24h so the next start doesn't re-surface immediately.

Before the four options, a cwd guard compares pwd against the saved saved-from-cwd. If you cd'd into a different project mid-session, the surfaced handoff was captured elsewhere — pickup asks you to reconfirm before resuming here.

04 — security

Trusted routing, untrusted directive

A saved handoff is an installable, possibly-shared file — so handoff treats the directive as untrusted data. The SessionStart hook splits its output: the routing instruction and metadata are trusted; the directive content is wrapped in <untrusted-source> framing.

● trusted — emitted by the hook
  • Routing instruction — plain text telling Claude to invoke /handoff:pickup. Not inside the untrusted wrapper.
  • Metadatasaved-at, saved-at-sha, branch, cwd, progress tags, snooze status.
● untrusted — wrapped as data
  • Directive content — wrapped in <untrusted-source>, presented as data describing user intent, not instructions to act on.
  • Even on Execute, the directive is guidance for Claude to apply judgement to — never commands to mechanically run.

The split matters: an attacker who lands a malicious active.md can only influence directive content, not the routing instruction. The four-option AskUserQuestion ensures you confirm any action. If you don't recognize a saved handoff in your repo, pick Discard and investigate — though with .claude/handoff/ in .gitignore (the first save offers this), that's unlikely. Defense-in-depth for a publicly-installable plugin.

05 — reference

Skills & the hook

Four skills, all /handoff:<name>. save and pickup auto-invoke on intent; check auto-invokes but is read-only; discard is destructive and user-invoked only.

SkillInvocationWhat it does
/handoff:saveautoAuto-invokes on a wrap-up phrase; drafts a directive and confirms via AskUserQuestion before writing active.md. The slash form is the no-confirm fallback.
/handoff:pickupautoAuto-invokes after the hook surfaces a handoff. Re-reads from disk, runs the cwd guard, presents the four-option flow (Execute / Edit / Discard / Save for later).
/handoff:checkauto · read-onlyReports whether a handoff exists, its age, saved-at SHA/branch, how far the repo has moved past it, and snooze status. A sanity check for "why isn't this surfacing?".
/handoff:discarduser-onlyArchive the active handoff without acting — the same effect as picking Discard in pickup, but invocable from anywhere. disable-model-invocation.
HookEventRole
session-start.shSessionStartAuto-registered (no setup). Reads active.md, runs snooze + stale checks, computes git progress, and emits additionalContext routing Claude to pickup. Exits silent on the common no-handoff path.
06 — storage & config

One active slot, an archive, and a few knobs

Everything lives under .claude/handoff/ (the v0.2.0 folder layout): a single active handoff plus a timestamped archive. Settings are optional — defaults apply if absent.

active.md
The active handoff

One per repo (single-slot). Frontmatter holds saved-at, saved-at-sha, saved-at-branch, saved-from-cwd.

archive/consumed-<ts>.md
After Execute

The directive that was acted on, kept (renamed, not deleted) so it stays recoverable.

archive/discarded-<ts>.md
After Discard

Archived without acting — from the pickup flow or /handoff:discard.

archive/expired-<ts>.md
Stale auto-archive

Moved here once older than stale-day-threshold (default 90 days).

settings.md
Optional config

Override thresholds, snooze window, gitignore behavior, archive retention, and trigger phrases. Edit directly.

configurable knobs (settings.md frontmatter)
KeyDefaultEffect
stale-commit-threshold3Commits past saved-at-sha before the handoff is tagged "progress made" (a surface tag, not a behavior change).
stale-day-threshold90Days past saved-at before the hook silently auto-archives to expired-<ts>.md.
deferral-snooze-hours24Hours to suppress re-surface after "Save for later".
gitignore-promptaskask or never — whether the first save offers to add .claude/handoff/ to .gitignore.
archive-retention10Cap on archive file count (shared across consumed / discarded / expired). Special: 0, unlimited, -1.
trigger-phrases(built-in list)Additions/overrides for the save NL trigger whitelist.

Upgrading from 0.1.x? v0.2.0 moves every artifact into .claude/handoff/. There is no automatic detection of 0.1.x files — the README ships a one-liner to migrate the flat .claude/handoff.md layout and update the .gitignore pattern from .claude/handoff*.md to .claude/handoff/.

handoff · v1.0.0 · MIT · by Apurv Bazari