SLOPSHOPPER

fresh-ctx

Auto handoff at 250k tokens or 35% context: Claude writes a handoff, the context clears and the handoff reloads itself.

guardtoastprocess
★ 1v0.4.0MITupdated 2026-10-21hasuwini77/fresh-ctx
A shopper browsing a rack in a slop shop
README

fresh-ctx

Stops long Claude sessions from rotting. At 35% context or 250k tokens (whichever comes first) Claude finishes its current step and writes a handoff. Then the context is cleared and the handoff submitted as the next prompt automatically, and the work continues in a fresh window. Nothing to type, copy or paste.

Claude Code (terminal, or the Code tab in the desktop app): automatic

Inside Claude Code:

/plugin marketplace add hasuwini77/fresh-ctx
/plugin install fresh-ctx@fresh-ctx

Update later with /plugin marketplace update fresh-ctx. Restart Claude Code. From then on:

  1. Context crosses 35% or 250k tokens → a hook tells Claude to finish its current step, write a handoff and stop.
  2. When that turn ends, the fresh-ctx mod runs /clear and submits the handoff as the next prompt.
  3. Claude continues the job in a clean window. This repeats for as long as the job runs.

Type fresh ctx any time to hand off early.

Setting (env var)DefaultMeaning
FRESH_CTX_PCT35Trigger point, % of window
FRESH_CTX_TOKENS250000Trigger point in tokens; whichever of the two comes first wins
FRESH_CTX_RENUDGE50000If Claude keeps going after a nudge, nudge again every this many tokens
FRESH_CTX_MIN_GROWTH5A session that started from a handoff must grow this many points before handing off again (stops loops)
FRESH_CTX_WINDOWautoWindow size; read from the session, override only if needed
FRESH_CTX_DIR~/.claude/fresh-ctxWhere handoffs live (one per project folder)

Set these in ~/.claude/settings.json under "env".

Works in any permission mode: the mod approves only the fresh-ctx skill and its save script, so a handoff never waits on a prompt. Subagent tool calls are ignored.

Requires bash. Tested on Linux/WSL; macOS should work; native Windows is untested.

Claude chat app (claude.ai / desktop chat): manual

The chat app has no hooks and no /clear, so this version can't be automatic. Download fresh-ctx-chat.zip and upload it under Settings → Capabilities → Skills. Say fresh ctx (Claude also offers it when a chat gets long), copy the block it gives you, open a new chat, and paste it in.

Limits

  • Automatic clear needs a host that loads mods. Tested in the terminal CLI; the desktop app's Code tab is untested. Without mods, Claude stops and asks you to type /clear, and the handoff still reloads by itself.
  • Background jobs (builds, test runs) keep running across the clear; the fresh window gets their completion notice and the handoff lists them.
  • Pressing Esc on a turn skips the automatic step for that turn.
  • A handoff is loaded once, then archived as .md.loaded in ~/.claude/fresh-ctx/.
Source 1 files
hooks/register.ts 52 lines
1import type { Register } from 'claude-code'
2
3// The automatic half of fresh-ctx. When a main-thread turn ends:
4//  - a handoff is waiting for this session → /clear, then submit the handoff as the next prompt;
5//  - context is past the threshold with no handoff yet → ask Claude to write one (once per session).
6// Paths and env live in bash (hooks/mod.sh) so the mod and the shell hooks agree on them.
7const field = (input: unknown, key: string): string => {
8  const value = (input as Record<string, unknown> | null)?.[key]
9  return typeof value === 'string' ? value : ''
10}
11
12export const register: Register = on => {
13  const nudged = new Set<string>()
14
15  // The handoff must never wait on a permission prompt: allow the skill and its save script, nothing else.
16  on('tool.check', { tool: 'Skill' }, ($, e, next) =>
17    /(^|:)fresh-ctx$/.test(field(e.input, 'skill')) ? { decision: 'allow' } : next(e))
18  on('tool.check', { tool: 'Bash' }, ($, e, next) =>
19    field(e.input, 'command').startsWith(`bash "${$.plugin.root}/skills/fresh-ctx/save.sh"`) ? { decision: 'allow' } : next(e))
20
21  on('session.measure', async ($, e, next) => {
22    if (e.context.window) await $.process.run(['bash', `${$.plugin.root}/hooks/mod.sh`, 'window', String(e.context.window)])
23    return next(e)
24  })
25
26  on('turn.complete', async ($, e, next) => {
27    const done = await next(e)
28    if (e.agentId || e.isAborted) return done
29
30    const sid = await $.session.id()
31    const sh = (...args: string[]) => $.process.run(['bash', `${$.plugin.root}/hooks/mod.sh`, ...args])
32
33    void (async () => {
34      const handoff = (await sh('take', sid)).stdout.trim()
35      if (handoff) {
36        $.ui.toast('fresh-ctx: clearing context and reloading the handoff')
37        await $.command.run({ command: 'clear' })
38        await sh('mark', await $.session.id())
39        await $.prompt.submit({ text: `[fresh-ctx] Handoff from the previous context window. Continue from here; do not redo finished work.\n\n${handoff}` })
40        return
41      }
42      const { context } = await $.session.usage()
43      if (!context.tokens || nudged.has(sid)) return
44      if ((await sh('due', sid, String(context.tokens), String(context.window))).stdout.trim() !== 'yes') return
45      nudged.add(sid)
46      await $.prompt.submit({ text: `[fresh-ctx] Context is at ${context.percent}% (${context.tokens} tokens). Run the fresh-ctx skill now: write the handoff and stop.` })
47    })().catch(err => $.ui.toast(`fresh-ctx: ${String(err)}`))
48
49    return done
50  })
51}
52