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.
Installs into .claude/skills of the current project.
Are you the author of Usage Guard?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/shirley-xue-2025-usage-guard)
---
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:
1. **Follow this file** (you are reading it — treat that as the contract).
2. Run `arm.sh` via bash before heavy work (same as ARM MODE below).
3. 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
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` / `COOLDOWN` / `UNKNOWN` = 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.
- **`UNKNOWN`:** guard is blind (usage API returned no 5h percent for several polls) **or** the file is past `valid_until`. Do **not** start heavy work; tell user to run `claude login` or `usage-guard doctor`. Prefer `effective_state` if present — it already fails closed on staleness/blindness.
- **Freshness:** if `now` is past `valid_until` (or `stale: true`), treat as `UNKNOWN` even when `state` still says `RUN` (daemon may have died while numbers looked healthy).
- **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
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:
1. Before starting the main task loop
2. **Before dispatching each subagent or parallel batch**
3. After each subagent returns
4. Before starting any new multi-step tool sequence expected to take >10 minutes
5. 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`
1. Finish the **current atomic unit** only — do not abandon mid-write.
2. Update checkpoint with `done`, `next`, clear `note`.
3. 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.
4. **Do not** start new subagents, batches, or long tool chains this turn.
5. 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:
```text
/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.
6. 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:
```text
/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`
1. 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_*`).
2. Read checkpoint for `sitting_session_id` (or `checkpoint_writes_target` / primary `session_id` if no join).
3. Continue from `next`; dedupe using `done`.
4. 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.