A Claude Code plugin that makes Claude aware of how much of your plan usage it has burned, and — near the limit — makes it save its work and wait instead of dying mid-task.
The problem it solves: a long overnight run hits the 5-hour limit at 3am, the session stops, and nobody is awake to type "continue" when the window resets. Hours of working time are lost.
| usage | behaviour |
|---|---|
| < 85% | nothing, zero tokens spent |
≥ 85% (warnAt) |
one-line nudge into context: finish work in progress, keep the task list current |
≥ 95% (pauseAt) |
blocks the tool call, orders a checkpoint, then holds the session until the window resets and tells Claude to carry on |
The pause happens inside a hook, so:
- the same session survives — full context, no re-explaining, no
--resume - no tokens are spent while waiting
- work resumes automatically the moment the window rolls over
Claude Code's status line payload is the only local source of real plan
utilization — rate_limits.five_hour.{used_percentage,resets_at} and the same
for seven_day, straight from the server (the same numbers /usage shows).
With statusLine.refreshInterval it keeps arriving even while a session is
idle or held.
So /usage-guard install points statusLine at this plugin's feed script,
which records every payload and chains through whatever status line you
already had (your existing line still renders, with UG 5h:74% 7d:41%
appended). settings.json is backed up first; /usage-guard uninstall puts it
back.
Caveats, honestly:
- The feed only fills in after the first API response of a session, and only for Claude subscription auth — never for API key / Bedrock / Vertex, where plan limits don't apply anyway.
- Until a sample arrives, or if a sample goes stale (>
staleSeconds), the guard fails open: it never blocks work on data it doesn't trust. /usage-guard pollis an opt-in second feed that reads plan usage fromGET /api/oauth/usageusing the local OAuth token (macOS keychain or~/.claude/.credentials.json). It exists for headlessclaude -pruns that have no status line. This path is unverified — it was written from the CLI's own call site but never executed here, because reading the keychain was blocked during development. Try it before relying on it.
git clone https://github.com/ManvikPasula/usage-guard.git
cd usage-guard
claude plugin marketplace add ./ # "./", not "." — bare "." is rejected
claude plugin install usage-guard@usage-guard-localThe marketplace source is this directory, but installing copies it to
~/.claude/plugins/cache/usage-guard-local/usage-guard/<version>/. After
editing the source, bump version in .claude-plugin/plugin.json, then:
claude plugin marketplace update usage-guard-local
claude plugin update usage-guard@usage-guard-localThen, inside Claude Code:
/usage-guard install # wire the usage feed into the status line
/usage-guard status # confirm
/usage-guard status: usage, thresholds, session phase
/usage-guard on | off toggle the guard (hooks stay registered, do nothing when off)
/usage-guard install [interval=10] wire the status line feed
/usage-guard uninstall restore the previous status line
/usage-guard set k=v [k=v ...] change config
/usage-guard cancel release a session that is currently held
/usage-guard reset clear per-session guard state
/usage-guard doctor check the feed end to end
/usage-guard poll one-off read via /api/oauth/usage (opt-in)
~/.claude/usage-guard/config.json, editable with /usage-guard set:
| key | default | meaning |
|---|---|---|
enabled |
true |
master switch |
warnAt |
85 |
% at which to nudge |
pauseAt |
95 |
% at which to checkpoint + hold |
resumeBelow |
90 |
resume early if usage falls under this |
windows |
["five_hour","seven_day"] |
which plan windows to watch |
maxWaitSeconds |
21600 |
never hold longer than this (6h) |
resumeBufferSeconds |
90 |
grace added past resets_at |
pollIntervalSeconds |
20 |
how often a held session re-checks |
staleSeconds |
900 |
older sample ⇒ fail open |
checkpointToolBudget |
30 |
tool calls Claude gets to save state |
gitCommit |
false |
opt-in git add -A && git commit during checkpoint |
statuslineSegment |
true |
render the UG … segment |
notifyCommand |
null |
shell command run once when a pause starts; message in $USAGE_GUARD_MESSAGE |
maxResumesPerSession |
6 |
loop protection |
Example — pause earlier, and get a desktop notification:
/usage-guard set pauseAt=92 warnAt=80 notifyCommand=terminal-notifier -message "$USAGE_GUARD_MESSAGE"
PreToolUseseesfive_hourat 96%. It denies that one tool call and hands Claude checkpoint orders: no new work, no half-applied edits, update the task list, write a handoff note to~/.claude/usage-guard/handoffs/<session>.md, then runctl.mjs checkpoint-done.- During the checkpoint only save-the-work tools are allowed (Read/Write/Edit/ Bash/Grep/task tools). Web fetches, subagents and workflows are refused. There's a hard budget so a non-cooperative model can't spin here forever.
checkpoint-doneflips the session topaused. Whichever comes first — Claude's next tool call (PreToolUse) or the end of its turn (Stop) — sleeps untilresets_at + resumeBufferSeconds, polling every 20s.- On reset,
Stopreturnsdecision: "block"with instructions to re-read the handoff note and continue;PreToolUsesimply lets the held call through. Either way the same session keeps going.
Both hooks are registered with a ~6h timeout, which is what makes the hold
legal — Claude Code's per-hook timeout is in seconds with no upper clamp.
Esc/Ctrl-Caborts a hold immediately./usage-guard cancelfrom another terminal releases it within one poll./usage-guard offdisables everything and releases any held session.- A weekly (
seven_day) limit whose reset is days out is not waited on: anything beyondmaxWaitSecondsis refused, and Claude is told to stop and report instead.
- Never returns
permissionDecision: "allow"— your normal permission prompts are untouched. The guard only ever denies or stays silent. - Never commits to your repo unless you set
gitCommit=true. Files on disk are not at risk from a pause; a half-finished edit and lost intent are, which is what the checkpoint targets. - Every failure path is fail-open: a broken guard must not break a session.
.claude-plugin/plugin.json hook registrations (incl. the multi-hour timeouts)
.claude-plugin/marketplace.json
commands/usage-guard.md the /usage-guard slash command
scripts/lib.mjs config, state, usage-feed normalisation
scripts/statusline.mjs the feed: records rate_limits, chains your old line
scripts/gate.mjs PreToolUse: warn / checkpoint / hold
scripts/stop.mjs Stop: hold past the end of a turn, then resume
scripts/pause.mjs the wait loop
scripts/session-start.mjs SessionStart: state + context priming
scripts/prompt.mjs UserPromptSubmit: near-limit nudge
scripts/ctl.mjs CLI behind the slash command
State lives in ~/.claude/usage-guard/: config.json, usage.json (latest
sample), sessions/*.json, handoffs/*.md, log.jsonl, timestamped
settings.backup.*.json, and two generated shims — feed.mjs (what
settings.json calls) and ctl.mjs (what the slash command calls). The shims
resolve pluginRoot from config.json at run time, which SessionStart
rewrites whenever the plugin's versioned path changes, so a plugin update can't
leave a dangling status line.
scripts/ was exercised end to end against an isolated CLAUDE_CONFIG_DIR:
feed capture and status-line chaining, install/uninstall round-trip on a
pre-existing status line, silent below warnAt, warning at warnAt, deny +
checkpoint orders at pauseAt, allow/deny split during a checkpoint, budget
exhaustion, checkpoint-done → hold → resume via both the Stop and
PreToolUse paths, early release via cancel, refusal to wait on a
days-away weekly reset, fail-open on a stale sample, the off switch, resume
cap, config validation, and doctor. /usage-guard poll is the one path that
has not been run — see the caveat above.