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

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.
⚠ 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.
git clone https://github.com/DaKev/usage-wrapup ~/claude-mods/usage-wrapup
claude --plugin-dir ~/claude-mods/usage-wrapup
/config → Wrap-up threshold (% left). Default 5.
claude plugin validate .
claude plugin test .
MIT license. See LICENSE.
hooks/register.ts 68 lines1import 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