| name | usage-guard |
| description | Arm proactive 5-hour usage protection for long Claude Code sessions (batch, subagents, high window % at start). Daemon reads account-level 5-hour window usage (not per-session). User types /usage-guard at session start. Re-arm every sitting. If user mentions usage-guard by name, read this file and run arm.sh. |
| disable-model-invocation | true |
| user-invocable | true |
| allowed-tools | Bash, Read, Write, Edit |
| argument-hint | ["resume | your task description"] |
usage-guard
Proactive usage guard for long Claude Code sessions (Desktop Code tab or CLI). Invoke once at session start before heavy batch, subagent, or overnight work โ especially when the account window is already partly used.
Why account-level monitoring matters
Each Claude session only sees its own usage. The shared 5-hour window is account-level โ you can start at 58% in one session while another tab or earlier work already pushed the window to 94%. The daemon polls the OAuth usage API (same as Desktop Settings) so the guard sees the real window, not just this chat's view.
Re-arm every sitting
Arms do not survive Desktop quit, crash, or daemon exit. At the start of every new sitting, run /usage-guard (or /usage-guard resume if continuing a paused task). If arm.sh prints "warning", the previous guard was stale โ proceed with the new arm.
If the user mentions usage-guard without /usage-guard
When the user says "use usage-guard", "pay attention to usage limits", or names this skill but does not run the slash command:
- Follow this file (you are reading it โ treat that as the contract).
- Run
arm.sh via bash before heavy work (same as ARM MODE below).
- Tell the user:
/usage-guard is the normal entry point; with disable-model-invocation it may not show in the slash picker โ typing /usage-guard still works.
Do not auto-arm on your own initiative without the user asking for usage protection.
When the user invokes /usage-guard
Mode detection
- If the argument is
resume (or starts with resume): RESUME MODE (skip arm, go to section 4).
- Otherwise: ARM MODE.
1. ARM MODE โ start guard
Run:
bash "${CLAUDE_SKILL_DIR}/scripts/arm.sh" {{ARGUMENTS}}
If arm fails, stop and tell the user to run usage-guard doctor in Terminal. Do not start heavy work without a successful arm.
Read ~/.usage-guard/control.json once and confirm:
armed: true
phase is normal (or five_hour_percent is set) โ not stuck on waiting_first_poll without a live daemon
- If arm JSON output includes
"warning", tell the user the prior guard was not active and this is a fresh arm
- Report go/no-go using arm JSON:
five_hour_percent, five_hour_reset_local, seconds_until_five_hour_reset, state โ not raw UTC strings
- If
weekly_percent is present (weekly monitoring enabled in config), also report weekly % and weekly_reset_local / seconds_until_weekly_reset. Weekly can trigger PAUSE even when 5h looks safe.
- If present in control.json, also report extra usage wallet:
extra_used_credits, extra_monthly_limit, extra_utilization, extra_currency (amounts are in cents; usage-guard status formats them), or run usage-guard poll for a live snapshot
If arm JSON has "already_armed": true (daemon from a prior sitting):
- Proceed โ guard is active; do not
--force restart unless user asks.
- Same arc, new sitting โ
join.sh --task "label" (not arm with a task โ task is ignored on already_armed; JSON includes task_ignored if you tried).
- After join โ
checkpoint.sh writes to sitting_session_id automatically (stored in control.json). Do not use primary session_id unless you pass --session-id intentionally.
- Continuing paused work โ
/usage-guard resume (not join, not fresh arm).
Decision tree: already_armed + same sitting โ proceed on checkpoint_writes_target | new sitting same arc โ join.sh | paused task โ resume | dead daemon โ arm fresh
Warmup: right after arm, phase may be waiting_first_poll with null telemetry while the daemon fetches usage. arm.sh blocks up to ~45s for the first poll. If phase is still waiting_first_poll after arm returns, the daemon is warming up โ do not start heavy subagent batches yet; re-read control.json in a few seconds. If five_hour_percent stays null and daemon is dead (usage-guard status โ daemon not running), stop and run usage-guard doctor.
2. Session contract (inform up front โ before main work)
You are now usage-guard armed. Follow this for the rest of this session:
Control file (only signal that matters)
- Path:
~/.usage-guard/control.json
- Obey
state only: RUN = continue, PAUSE or COOLDOWN = stop dispatching new work.
- Either limit can pause: account 5-hour window (default 90%) or weekly limit (opt-in, default 98%) โ check
pause_reason (five_hour, weekly, or both) in control.json.
- Exception โ
telemetry_lost: true: usage API returned no 5h percent for several polls. Guard is blind โ do not start heavy or long work; tell user to run claude login or usage-guard doctor (they may get a macOS notification). state may still say RUN.
- Never decide pause/continue from
five_hour_percent yourself.
- Never compare timestamps yourself (
five_hour_resets_at, sleep_until, etc.). UTC trips up models. Use precomputed fields only:
seconds_until_five_hour_reset โ positive = reset not yet; negative or five_hour_reset_pending: false = reset time passed
five_hour_reset_local โ for user-facing reports (local timezone, not UTC)
weekly_percent, weekly_reset_local, seconds_until_weekly_reset โ when weekly monitoring is enabled (OAuth seven_day = Desktop "All models" weekly bar)
- If
state is RUN, continue โ do not call percent "stale" because a UTC clock comparison looked past. Wrong timezone math caused false "reset already passed" bugs.
- Post-reset poll lag:
five_hour_reset_pending: false + non-null five_hour_percent (e.g. 88% with reset time an hour ago) means the old window's percent until the daemon re-polls โ not a reason to pause. Check awaiting_post_reset_poll and percent_note; trust state. Expect correction at daemon_next_poll_at / daemon_next_poll_local.
- After a window reset,
five_hour_percent may be null until the daemon's next poll. If state is RUN, trust state (check last_reset_at if present) โ do not wait for the user to return.
session_check_seconds โ recommended delay for self-paced /loop guard ticks while armed. During PAUSE/COOLDOWN, prefer sleep_until instead. Daemon polling is separate (daemon_next_poll_at).
Checkpoint file
- Path:
~/.usage-guard/sessions/<session_id>/checkpoint.json (session_id from control.json)
- After every completed unit (file, batch item, subagent return): update checkpoint (
done, next, note).
- Prefer bash (atomic, no Read-before-Write harness issue):
bash "${CLAUDE_SKILL_DIR}/scripts/checkpoint.sh" --quiet --json '{"done":["item-id"],"next":"next-item","note":""}'
Or flags: checkpoint.sh --quiet --done item-id --next "next-item" --note "" --task "short label for status"
Set task on the first checkpoint (via /usage-guard <task> at arm, or checkpoint.sh --task) so usage-guard status stays readable.
Multi-session arcs (orchestrator + worker sessions)
PAUSE/RUN is account-level (one daemon). Checkpoints are per session_id.
- First sitting:
/usage-guard <task> (arms daemon; sets primary session_id and sitting_session_id).
- Later sitting (daemon already running):
join.sh --task "this sitting's label" โ sets sitting_session_id in control.json; all checkpoint.sh writes go there until the next arm/join.
- To share one checkpoint chain across sittings (advanced): pass
--session-id <existing> to checkpoint.sh explicitly.
- join = new sitting, new checkpoint file, same daemon. resume = continue a paused task from checkpoint (COOLDOWN/PAUSE arc), not "daemon already live."
active_session_ids lists all sittings; sitting_session_id is the active checkpoint write target.
- User can run
usage-guard status in Terminal to see progress if Desktop UI is stuck.
Safe checkpoints (critical for subagents / long runs)
Read control.json only at these times:
- Before starting the main task loop
- Before dispatching each subagent or parallel batch
- After each subagent returns
- Before starting any new multi-step tool sequence expected to take >10 minutes
- At each
/loop iteration dedicated to guard checks
Never rely on the user sending a chat message mid-run โ messages queue during subagent work.
On state == PAUSE or COOLDOWN
-
Finish the current atomic unit only โ do not abandon mid-write.
-
Update checkpoint with done, next, clear note.
-
Tell the user: paused near usage limit (pause_reason); daemon may macOS-notify at reset. Do not treat that as a substitute for scheduling your own wake-up below.
-
Do not start new subagents, batches, or long tool chains this turn.
-
During COOLDOWN, use sleep_until / seconds_until_five_hour_reset for wait duration โ do not poll every 5 minutes.
Before ending this turn, you must schedule a wake-up. Forbidden: ending with "ping me when reset", "tell me when you're back", or "message me after the window resets".
Pick one path (in order of preference):
A. /loop guard tick (preferred) โ any session type:
/loop Read ~/.usage-guard/control.json. If PAUSE/COOLDOWN: checkpoint if needed; sleep until sleep_until (cap 59m per iteration; chain). If RUN: continue from checkpoint next.
Dynamic /loop (no interval) uses ScheduleWakeup internally after each iteration.
B. One-shot wake at reset โ if you are not starting /loop:
- Natural language:
in N minutes, re-read ~/.usage-guard/control.json; if RUN continue checkpoint next; if still COOLDOWN schedule again
- Or
CronCreate one-shot pinned to five_hour_reset_local (+ ~3 min margin)
- Claude may implement this via
ScheduleWakeup or CronCreate โ either is fine
- Chain: if still
COOLDOWN after fire, schedule the next check (cap 59m steps)
C. Non-negotiable: schedule something before ending the turn. Platform docs emphasize ScheduleWakeup for dynamic /loop, but it often works for one-shot waits too โ never fall back to asking the user to ping you.
-
If wake scheduling fails (cron disabled, provider lacks loop-dynamic), say so explicitly and give five_hour_reset_local โ checkpoint is still safe for /usage-guard resume.
On state == RUN with existing checkpoint
Continue from next in checkpoint; skip items in done.
Subagent discipline (long batch runs)
- Prefer smaller subagent batches when armed (e.g. 3โ5 items, not 50).
- Before each dispatch: read control.json; if not
RUN, do not dispatch.
- Main chat orchestrates workload so subagents respect the timetable โ no mid-run chat reminders.
Session wrap โ report window delta
At session end (or before a long pause), tell the user:
- 5h window now:
five_hour_percent, five_hour_reset_local, state
- Weekly limit (if enabled):
weekly_percent, weekly_reset_local, pause_reason
- Extra usage wallet (if enabled):
extra_used_credits / extra_monthly_limit / extra_utilization from control.json, or usage-guard status for formatted amounts
- Delta this sitting if you recorded percent at arm/join (e.g. "4% โ 41% this session")
- This is the most useful user-facing summary โ report it even without subagents
3. Start work
If the user provided a task in /usage-guard <task>, begin it under the contract above using self-paced /loop.
Example loop prompt:
/loop Read ~/.usage-guard/control.json. If PAUSE/COOLDOWN: checkpoint and stop new work; sleep until sleep_until (or session_check_seconds). If RUN: continue task from checkpoint.
4. RESUME MODE โ /usage-guard resume
- Read
~/.usage-guard/control.json โ if state is not RUN, tell user to wait (show five_hour_reset_local / sleep_until_local, or seconds_until_*).
- Read checkpoint for
sitting_session_id (or checkpoint_writes_target / primary session_id if no join).
- Continue from
next; dedupe using done.
- Re-arm only if
armed is false: run arm.sh again.
5. Limits (be honest with user)
- Cannot force-stop a running subagent from outside; guard prevents new work after PAUSE.
- Progress safety depends on frequent checkpoint writes.
- After force-quit Desktop, use
/usage-guard resume in a new session (re-arms daemon if needed).
- Daemon exit does not keep the guard active โ always
/usage-guard or /usage-guard resume at the start of a sitting.