SLOPSHOPPER

next

Hand off to a fresh session: /next:go writes a handoff note, clears the context, and resumes from the note.

newguardtoast
v0.1.0Apache-2.0updated 2026-10-04pablofontanilla/claude-next/plugins/next
A shopper browsing a rack in a slop shop
README

next

A Claude Code plugin that hands a session off to a fresh one.

/next:go writes a short handoff note (goal, state, decisions, next task), clears the context, and starts a new session that reads the note and continues with the next task. You do not confirm anything.

Install

/plugin marketplace add pablofontanilla/claude-next
/plugin install next@pablofontanilla

Then restart Claude Code.

Requirements:

  • A Claude Code version that supports plugin function hooks (tested on 2.1.289). On older versions the note is still written and loads on your next /clear, but the automatic clear does not happen.
  • python3 on your PATH.

Use

/next:go
/next:go rerun the failing e2e with the new fixture

The optional text sets the next task.

How it works

  • The skill writes the note to ~/.claude/next/<key>.md (one note per launch directory) and arms it with a 60-minute expiry.
  • When the turn ends, the plugin runs /clear and then sends a prompt that starts with "Starting from a new session".
  • A SessionStart hook injects the note into the new session and moves it to <key>.consumed.md, so it fires only once.

Nothing is written inside your repository.

Feedback

Open an issue in this repository.

Source 2 files
hooks/register.ts 60 lines
1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import type { Phase } from '../types'
5
6const SKILL = 'next:go'
7// The first line is for the person: the /clear itself prints nothing.
8const RESUME =
9  'Starting from a new session: the previous context was cleared.\n\n' +
10  'Start the "Next task" from the handoff note loaded at session start now. ' +
11  'Do not ask for confirmation first.'
12
13// `python3 ".../handoff.py" arm --project-dir ...`
14const ARM = /handoff\.py["']?\s+arm\b/
15const OK = /"status"\s*:\s*"ok"/
16
17const phase = atom({ plugin: 'next', key: 'phase' } as const, 'idle' as Phase)
18
19export const register: Register = on => {
20  on('command.run', { command: SKILL }, async ($, e, next) => {
21    await update($, phase, () => 'running')
22    return next(e)
23  })
24
25  on('tool.call', { tool: 'Bash' }, async ($, e, next) => {
26    const ran = await next(e)
27    if (!ARM.test(e.command) || (await read($, phase)) !== 'running') return ran
28
29    const isArmed = ran.deny === undefined && ran.isError !== true && OK.test(ran.text ?? '')
30    if (isArmed) await update($, phase, () => 'armed')
31
32    return ran
33  })
34
35  on('turn.complete', async ($, e, next) => {
36    const done = await next(e)
37    if (e.agentId !== undefined) return done
38
39    const now = await read($, phase)
40    if (now === 'idle') return done
41
42    await update($, phase, () => 'idle')
43    if (now !== 'armed' || e.reason !== 'answer') {
44      $.ui.toast('next: the handoff was not armed. The session stays as it is.')
45      return done
46    }
47
48    $.ui.toast('next: handoff armed. Clearing and resuming…')
49    // Queued: both run once the session is idle, after this turn ends.
50    void $.command
51      .run({ command: 'clear' })
52      .then(() => $.prompt.submit({ text: RESUME }))
53      .catch((err: unknown) => {
54        $.ui.toast(`next: clear or resume failed: ${String(err)}. Run /clear by hand.`)
55      })
56
57    return done
58  })
59}
60
types/index.d.ts 10 lines
1// idle: nothing pending. running: /next:go is in its turn.
2// armed: the skill's `handoff.py arm` call returned status ok this turn.
3export type Phase = 'idle' | 'running' | 'armed'
4
5declare module 'claude-code' {
6  interface PluginState {
7    next: { phase: Phase }
8  }
9}
10