Two things, in one repo:
- A CLI (
claude_usage.py) that fetches your Claude.ai plan usage (the same data shown by Claude Code's/usagecommand) and prints it as JSON, so you can pipe it intojq, dashboards, status bars, etc. - A Claude Code skill (
/wait-for-token-usage) built on top of that CLI, which makes Claude itself aware of how much of your 5-hour window is left. Instead of getting cut off mid-task when the burst limit hits, the agent stops a little early, sleeps at zero token cost until the window resets, and then picks the work back up on its own. That lets a single session keep working across several 5-hour windows, unattended.
The CLI reads your OAuth token automatically from the local Claude Code credentials file at
~/.claude/.credentials.json โ no setup, no env vars required as long as you're logged into Claude
Code on this machine.
Jump to the skill if that's what you came for.
Built and documented with the help of Claude Code.
Requirements
- Python 3.7+
platformdirs(pip install platformdirs) for resolving the cache directory- A working Claude Code login on this machine (i.e.
~/.claude/.credentials.jsonexists and contains a validclaudeAiOauth.accessToken) - A Claude.ai subscription (Pro/Max/Team). The endpoint does not work with a
plain
ANTHROPIC_API_KEY.
Install
Clone or download, then make the script executable:
chmod +x claude_usage.py
That's it. You can optionally symlink it onto your PATH:
ln -s "$PWD/claude_usage.py" ~/.local/bin/claude-usage
Usage
# Compact one-line JSON (pipe-friendly) python claude_usage.py # Indented for humans python claude_usage.py --pretty # Keep reset times as raw UTC ISO-8601 instead of local human-readable time python claude_usage.py --utc # Include local token metadata under a _local key python claude_usage.py --include-token-meta # Use a different credentials file python claude_usage.py --credentials /path/to/.credentials.json # or via env var: CLAUDE_CREDENTIALS=/path/to/.credentials.json python claude_usage.py # Disable auto-refresh of expired tokens (see "Auto-refresh" below) python claude_usage.py --no-autorefresh # Change the local cache TTL (default 300s, 0 to always re-fetch) python claude_usage.py --cache-ttl 60 # Threshold past which stale-cache fallback uses a louder warning (default 3600s) python claude_usage.py --stale-warn 900
Example output
{
"five_hour": { "utilization": 7.0, "resets_at": "Sat 25 Apr 2026, 22:40 (CEST)" },
"seven_day": { "utilization": 42.0, "resets_at": "Sun 26 Apr 2026, 19:00 (CEST)" },
"seven_day_oauth_apps": null,
"seven_day_opus": null,
"seven_day_sonnet": null,
"extra_usage": {
"is_enabled": false,
"monthly_limit": null,
"used_credits": null,
"utilization": null,
"currency": null
}
}utilization is a percentage from 0 to 100. resets_at is, by default,
converted from the API's UTC time to your machine's local timezone and
formatted for humans (e.g. Sun 26 Apr 2026, 19:00 (CEST)). Pass --utc to
keep it as a raw ISO-8601 UTC timestamp instead. Every resets_at in the
payload is converted, including the entries under limits[].
extra_usage.monthly_limit and used_credits are in cents when present:
divide by 100 for dollars.
The exact set of keys you get depends on your plan; on a Pro account, only
five_hour, seven_day, and extra_usage are populated. On Max/Team you may
also see seven_day_opus and seven_day_sonnet.
Recipes
Show "X% of weekly used, resets in Yh"
python claude_usage.py | jq -r ' .seven_day | "\(.utilization)% used ยท resets at \(.resets_at)" '
Exit non-zero if the 5-hour window is above 90%
python claude_usage.py \ | jq -e '.five_hour.utilization < 90' > /dev/null
Tmux / shell prompt status
python claude_usage.py | jq -r '"\(.five_hour.utilization|floor)%/5h \(.seven_day.utilization|floor)%/7d"'
Bundled Claude Code skill: /wait-for-token-usage
skills/wait-for-token-usage/ is an agent skill
that turns this script into a budget-aware working mode for long Claude Code sessions: it polls the
5-hour and weekly windows, refuses to start expensive work when the burst window is nearly spent,
parks the session in a zero-token background wait until resets_at, and resumes automatically once
the window has rolled over.
The practical effect: one long task can span several 5-hour windows without you babysitting it, and without the agent being interrupted mid-edit by a rate limit.
Install it by symlinking the directory into your skills folder, so it stays in sync with the repo:
ln -s "$PWD/skills/wait-for-token-usage" ~/.claude/skills/wait-for-token-usage
The slash command comes from the symlink name, not from the name: in the frontmatter, so name
the symlink whatever you want to type. ~/.claude/skills/park-until-reset gives you
/park-until-reset.
Then invoke it explicitly, e.g. /wait-for-token-usage. It is deliberately opt-in: the skill tells
the agent not to apply this mode unless you asked for it, because parking a session for hours should
be your call.
The skill calls claude-usage --utc --no-autorefresh (see the symlink recipe in
Install) and falls back to reading the cache file directly when sandbox rules hide the
repo from the agent's Bash tool.
Local paths without committing them
The skill loads an optional personalisation.md from its own directory, which is gitignored, so
your machine-specific bits stay out of the repo:
cp skills/wait-for-token-usage/personalisation.example.md \
skills/wait-for-token-usage/personalisation.md
$EDITOR skills/wait-for-token-usage/personalisation.mdUse it for the exact command that runs claude_usage.py here (interpreter, absolute path), for a
note about paths your agent's sandbox cannot read, and for threshold overrides. Anything it says
wins over the defaults in SKILL.md. If the file does not exist, the skill just uses its defaults.
How it works
The script:
- Reads
~/.claude/.credentials.jsonand pulls outclaudeAiOauth.accessToken. - If the
expiresAttimestamp is in the past, refreshes the token first (see Auto-refresh); with--no-autorefreshit just prints a warning to stderr and attempts the request anyway. - Sends
GET https://api.anthropic.com/api/oauth/usagewith these headers:Authorization: Bearer <token>anthropic-beta: oauth-2025-04-20User-Agent: claude-cli/...
- Prints the JSON response to stdout (or a stale cached response on any fetch failure, see Caching and rate limits).
The --include-token-meta flag adds a _local key containing
subscriptionType, rateLimitTier, and expires_in_seconds from your local
credentials file (these never go over the network).
Auto-refresh
By default (--autorefresh, on), the script will:
- Detect that the access token in
~/.claude/.credentials.jsonis expired, or receive a 401 from the API. - Launch
claudein the background for ~20 seconds and then terminate it. Starting a real Claude Code session causes it to refresh the access token using the stored refresh token and write the new token back to the credentials file. (claude --versiondoes not trigger a refresh.) - Re-read the credentials file and retry the request once.
Why delegate to claude instead of refreshing the token ourselves?
- It avoids racing with Claude Code on the credentials file.
- It avoids racing with Claude Code on refresh-token rotation โ if we refreshed and the server invalidated the old refresh token, Claude Code could end up logged out.
- The OAuth refresh endpoint isn't documented; letting the official client own that flow keeps the script much simpler and less brittle.
Pass --no-autorefresh to disable this (e.g. if claude is not on PATH, or
if you want the script to fail fast on 401 for monitoring purposes).
Caching and rate limits
/api/oauth/usage is rate-limited per token. Calling it on a tight loop (tmux
status bar, shell prompt, watch loop, while testing) will trip the limit and
lock you out for up to an hour. To avoid this, the script keeps a small local
cache under the platform's user cache directory (resolved via
platformdirs, so on Linux this
honours XDG_CACHE_HOME and is typically ~/.cache/claude_usage/usage.json):
- On every run, if the cache is younger than
--cache-ttlseconds (default 300, i.e. 5 minutes), the cached response is returned without hitting the API. - On a successful fetch, the cache is overwritten.
- On any fetch failure (
429 Too Many Requests, other non-2xx HTTP responses, network errors), the script falls back to the stale cache if one exists and prints the cached payload to stdout with a warning on stderr, so status bars keep working through outages and rate-limit windows. If no cache exists, it exits with code 1. - The stale-fallback warning is prefixed
warning:if the cache is fresher than--stale-warnseconds (default 3600), and the louderWARNING:otherwise, so monitoring tools can distinguish "briefly stale" from "dangerously stale".
Use --cache-ttl 0 if you need the freshest possible value and accept the
risk of getting rate limited. The cache is still written on success so the
failure-time fallback remains available.
Limitations
- OAuth-only. This endpoint does not accept a plain Anthropic API key.
- Subscription-gated. Free-tier accounts will likely get an empty response or 401.
claudemust be on PATH for--autorefreshto work. If it isn't, pass--no-autorefresh.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Success (fresh response, served cache, or stale-cache fallback after a failed fetch) |
| 1 | Fetch failed (network error or non-2xx HTTP response) and no cache available to fall back on |
| 2 | Credentials file missing or malformed |
Related
The endpoint and headers were reverse-engineered from the Claude Code CLI
binary. The same data backs the /usage slash command in Claude Code itself.