/handoff-clear: write a handoff, /clear, and resume from it in the fresh context

One command for the handoff -> /clear -> paste-resume-prompt routine.
/handoff-clear [what to stress] asks Claude to save a handoff for the current task (using a handoff skill if you have one, otherwise a built-in spec: a Markdown file under docs/handoffs/ or ~/.local/state/agent-handoffs/) and to end its reply with HANDOFF-PATH: <file>. When that turn ends the mod checks the file exists, runs /clear, and submits "Continue the task described in <file>..." into the fresh context.
A missing marker or file, or an interrupted turn, aborts with a toast starting handoff-clear: and nothing is cleared. Prompts the mod submits show as "The handoff-clear plugin sent a message".
/plugin install handoff-clear --marketplace jaromirsvoboda/claude-mods
or from a shell: claude plugin install handoff-clear --marketplace jaromirsvoboda/claude-mods.
hooks/register.ts 87 lines1import type { EngineInterface, Register } from 'claude-code'
2
3// One user command for the handoff -> /clear -> paste-resume-prompt routine.
4// The model writes the handoff (so it stays a file you can read and edit);
5// the mod only sequences the three steps and refuses to clear unless the
6// handoff file it was told about actually exists.
7
8const MARKER = /^HANDOFF-PATH:\s*(\S+)\s*$/m
9
10const handoffPrompt = (focus: string) =>
11 [
12 'Use the handoff skill now: save a durable handoff for the current task,',
13 'because this context will be cleared as soon as you finish.',
14 'If no handoff skill is available, write the file yourself to',
15 'docs/handoffs/YYYY-MM-DD-HHMM-<topic>.md in the project (or',
16 '~/.local/state/agent-handoffs/ outside one): objective and constraints,',
17 'current state (branch, changed files), decisions and evidence, checks run,',
18 'remaining work in order, and the first command to verify on resume.',
19 focus ? `Emphasis from the owner: ${focus}` : '',
20 'Do not ask me to /clear or paste anything; that happens automatically.',
21 'End your reply with one final line, exactly:',
22 'HANDOFF-PATH: <absolute path of the saved handoff file>',
23 ]
24 .filter(Boolean)
25 .join('\n')
26
27const resumePrompt = (path: string) =>
28 `Continue the task described in ${path}. Read applicable project instructions ` +
29 'and that handoff first. Verify the current working tree and live state before ' +
30 'acting; preserve unrelated changes. Resume at the first remaining step it lists.'
31
32// The engine refuses $.prompt.submit / $.command.run made from inside a
33// command.run or turn.complete hook (they would wait on the turn the hook
34// holds), so each step is fired from a timer, a later event of its own.
35function later($: EngineInterface, step: () => Promise<unknown>, onError: () => void) {
36 $.clock.after(50, () => {
37 step().catch(err => {
38 onError()
39 $.ui.toast(`handoff-clear: ${String(err)}`)
40 })
41 })
42}
43
44export const register: Register = on => {
45 // Module variable, not $.state: the mod is not reloaded by /clear, and the
46 // flag must not outlive a reload half-way through.
47 let armed = false
48
49 on('session.start', async ($, e, next) => {
50 await $.command.register({
51 name: 'handoff-clear',
52 description: 'Write a handoff, /clear, and resume from it automatically',
53 argumentHint: '[what the handoff should stress]',
54 })
55 return next(e)
56 })
57
58 on('command.run', { command: 'handoff-clear' }, async ($, e) => {
59 armed = true
60 const focus = e.args.trim()
61 later($, () => $.prompt.submit({ text: handoffPrompt(focus) }), () => (armed = false))
62 return { text: 'writing the handoff; will /clear and resume once it is saved.' }
63 })
64
65 on('turn.complete', async ($, e, next) => {
66 const done = await next(e)
67 if (!armed || e.agentId !== undefined) return done
68 armed = false
69
70 const path = e.reason === 'answer' ? e.answer.match(MARKER)?.[1] : undefined
71 if (!path || !(await $.fs.exists(path))) {
72 $.ui.toast(
73 path
74 ? `handoff-clear: ${path} does not exist; not clearing.`
75 : 'handoff-clear: no HANDOFF-PATH line (or turn interrupted); not clearing.',
76 )
77 return done
78 }
79
80 later($, async () => {
81 await $.command.run({ command: 'clear', args: '' })
82 await $.prompt.submit({ text: resumePrompt(path) })
83 }, () => {})
84 return done
85 })
86}
87