SLOPSHOPPER

usage-wrapup

When your plan usage gets low, tells every running agent to wrap up before the limit cuts it off.

newguardtoaststatus
★ 1v0.1.0MITupdated 2026-10-01DaKev/usage-wrapup
A shopper browsing a rack in a slop shop
README

usage-wrapup

A Claude Code mod. When your plan usage gets low, it tells every running agent to wrap up — so you don't get cut off mid-task with half-edited files and no handoff.

What it does

  • Watches your rate-limit windows (5-hour, weekly, spend limit).
  • When any window has 5% or less left (configurable), every tool result — in the main agent and in subagents — carries a note telling the model to:
  • start no new work,
  • finish or safely pause the current step,
  • write a short handoff (done / left / next step),
  • stop and answer.
  • Shows ⚠ N% left — wrapping up in the status line and a one-time toast.

Only works on a Claude subscription (Pro/Max/Team), where Claude Code gets rate-limit data.

Install

git clone https://github.com/DaKev/usage-wrapup ~/claude-mods/usage-wrapup
claude --plugin-dir ~/claude-mods/usage-wrapup

Configure

/config → Wrap-up threshold (% left). Default 5.

Test

claude plugin validate .
claude plugin test .

MIT license. See LICENSE.

Source 1 files
hooks/register.ts 68 lines
1import type { Register, SessionRateLimit } from 'claude-code'
2
3const LABEL: Record<string, string> = {
4  five_hour: '5-hour',
5  seven_day: 'weekly',
6  spend_limit: 'spend',
7}
8
9type Low = { kind: string; left: number; resetsAt?: string }
10
11/** The window with the least left, when it is at or below the threshold. */
12export function lowest(limits: readonly SessionRateLimit[], threshold: number): Low | undefined {
13  let worst: Low | undefined
14  for (const l of limits) {
15    const left = Math.max(0, Math.round((100 - l.percentUsed) * 10) / 10)
16    if (!worst || left < worst.left) worst = { kind: l.kind, left, resetsAt: l.resetsAt }
17  }
18  return worst && worst.left <= threshold ? worst : undefined
19}
20
21function describe(low: Low): string {
22  const name = LABEL[low.kind] ?? low.kind
23  const reset = low.resetsAt ? ` It resets at ${low.resetsAt}.` : ''
24  return `${name} usage limit: only ${low.left}% left.${reset}`
25}
26
27export function wrapUpNote(low: Low): string {
28  return [
29    `USAGE LOW — ${describe(low)}`,
30    'You may be cut off soon. Wrap up now:',
31    '1. Do not start new work, new subagents or big searches.',
32    '2. Finish or safely pause the current step. Leave files in a working state.',
33    '3. Write a short handoff: what is done, what is left, the exact next step.',
34    '4. Then stop and give the user your final answer.',
35  ].join('\n')
36}
37
38export const register: Register = (on, options) => {
39  const threshold = Number(options.threshold ?? 5)
40  let limits: readonly SessionRateLimit[] | undefined
41  let warned = false
42
43  on('session.measure', ($, e, next) => {
44    limits = e.rateLimits
45    const low = lowest(limits, threshold)
46    $.ui.status(low ? `⚠ ${low.left}% left — wrapping up` : undefined)
47    if (low && !warned) $.ui.toast(`usage-wrapup: ${describe(low)} Agents told to wrap up.`)
48    warned = !!low
49    return next(e)
50  })
51
52  // Every tool result (main loop and subagents) carries the note while usage is low.
53  on('tool.call', async ($, e, next) => {
54    const ran = await next(e)
55    if ('deny' in ran && ran.deny !== undefined) return ran
56    if (!limits) {
57      try {
58        limits = (await $.session.usage()).rateLimits
59      } catch {
60        limits = []
61      }
62    }
63    const low = lowest(limits, threshold)
64    if (!low) return ran
65    return { ...ran, context: [...(ran.context ?? []), wrapUpNote(low)] }
66  })
67}
68