Show live session/weekly usage % and reset countdown in CLI (e.g. /usage)

Status Fixed / completed
Maintainer reply None cached
Activity 5 comments · opened Jul 17, 2026 · closed Aug 17, 2026

Is your feature request related to a problem? Please describe.
The claude.ai web/desktop app's Settings -> Usage page shows two useful pieces of live info:

  1. Current session usage as a percentage bar with a countdown to reset (e.g. 96% used - Resets in 33 min)
  2. Weekly limit usage as a percentage bar with the next reset date/time (e.g. 38% used - Resets Mon 3:30 AM)

Claude Code's /usage command shows usage percentages but not a live reset countdown or reset timestamp. Currently the only way to see a reset time is to hit the limit and read it off the resulting error message (e.g. resets 3:45pm), which is too late to be useful for pacing work.

Describe the solution you'd like
Extend /usage (or the statusline) to always display:

  • Current session: usage % and time remaining until reset (live countdown)
  • Weekly limit (all models): usage % and next reset date/time

This mirrors what's already shown on claude.ai/settings/usage, just surfaced in the terminal so users don't have to alt-tab to a browser to plan around limits.

Describe alternatives you've considered
Checking claude.ai/settings/usage manually in a browser; waiting to hit the limit to see the reset time via the error message.

Additional context
This would help users self-pace long sessions and avoid hitting the cap mid-task.

View original on GitHub ↗

3 Comments

eltonylfgi-blip · 1 month ago

I hit the same pacing gap and built a local workaround while the native /usage view is missing these countdowns.

usage-guard snapshots Claude Code's own status-line rate_limits and shows both the 5-hour and weekly percentage + reset countdown in /usage-guard:usage. It can also warn at a configurable threshold, so you see the cliff before the error message.

After installing the plugin, /usage-guard:setup wires the real-quota capture; no manual JSON path hunting. It is zero-dependency, local-first, and makes no network calls by default.

Important limitation: this does not improve Claude Code's native /usage, and the real numbers still depend on Claude Code exposing rate_limits after the first response. The upstream request here is still useful.

Disclosure: I maintain usage-guard.

rssrn · 17 days ago

Building on @eltonylfgi-blip's point that the status-line payload carries rate_limits — for anyone who'd rather not add a plugin, here's a brief recipe for surfacing these figures with a hook: about six lines of shell, nothing to install.

As of 2.1.231 the JSON piped to a statusLine command includes:

{
  "context_window": { "used_percentage": 7, "context_window_size": 1000000 },
  "rate_limits": {
    "five_hour": { "used_percentage": 83, "resets_at": 1786641000 },
    "seven_day": { "used_percentage": 76, "resets_at": 1786809600 }
  }
}

The key detail for this request: resets_at is a Unix timestamp, so the reset time the OP wants is just date -d @…. That's what closes the "only way to see a reset time is to hit the limit" gap.

#!/bin/bash
# ~/.claude/hooks/statusline.sh   (chmod +x)
INPUT=$(cat)
jq -r '
  "\(.model.display_name)"
  + "  ctx \(.context_window.used_percentage)%"
  + "  5h \(.rate_limits.five_hour.used_percentage)%"
  + "  7d \(.rate_limits.seven_day.used_percentage)%"
' <<< "$INPUT"

And in settings.json:

{ "statusLine": { "type": "command", "command": "~/.claude/hooks/statusline.sh" } }

Renders as Opus 5 ctx 7% 5h 83% 7d 76%.

<details>
<summary>The fuller version I actually use — adds severity colouring, reset times, and git branch/worktree state</summary>

#!/bin/bash
# Footer status line: model, git branch, then the three budgets that can stop
# work — context window, 5-hour rate limit, 7-day rate limit.
#
# Renders as:   Opus 5 · main · ctx 7% · 5h 83% @18:10 · 7d 76% @Sat 17:00
#               Opus 5 · main*                   uncommitted (tracked) changes
#               Opus 5 · openrouter-gap ⑂        session is in a linked worktree
#               Opus 5                           not a git repo, no budget data
#
# The figures are always shown so their position is predictable, and coloured by
# severity so a glance is enough: dim while fine, amber past the first threshold,
# red past the second. A rate limit also gains its reset time once it turns
# amber, because at that point "when does this clear?" is the actual question.
#
# The rate limit percentages come from .rate_limits in the statusline payload,
# alongside .context_window — the same figures /usage reports, but available to
# a hook. Confirmed present in Claude Code 2.1.231. Note that only the aggregate
# five_hour/seven_day windows are exposed; per-model buckets are still missing
# (anthropics/claude-code#79022, #84280).
#
# "Dirty" here means tracked changes only (--untracked-files=no), so the `*`
# stays quiet about untracked files and appears once something under version
# control has actually been modified.
#
# @author Claude Opus 5 Anthropic
INPUT=$(cat)

# Joins a rate limit to its reset time, as in "5h 88% @18:10". Kept as a constant
# because it is pure taste and font-dependent: "→" is the obvious choice but is
# uncomfortably wide in some terminal fonts. Alternatives that fit: " → ", "~",
# " ends ".
RESETS_AT_SEP=' @'

DIM=$'\033[2m'
AMBER=$'\033[33m'
RED=$'\033[31m'
RESET=$'\033[0m'

# Colour a fragment by how close its value is to being a problem.
# @author Claude Opus 5 Anthropic
severity() {
    local text="$1" value="$2" amber="$3" red="$4"
    if [ "$value" -ge "$red" ]; then
        printf '%s' "${RED}${text}${RESET}"
    elif [ "$value" -ge "$amber" ]; then
        printf '%s' "${AMBER}${text}${RESET}"
    else
        printf '%s' "${DIM}${text}${RESET}"
    fi
}

# A rate limit renders as "5h 83%", gaining " @18:10" once it reaches amber.
# @author Claude Opus 5 Anthropic
rate_limit() {
    local label="$1" pct="$2" resets_at="$3" amber=70 red=90 text when

    [ -z "$pct" ] && return
    text="${label} ${pct}%"
    if [ "$pct" -ge "$amber" ] && [ -n "$resets_at" ]; then
        # A bare "17:00" is ambiguous for the 7-day window, whose reset is
        # usually days out — name the day unless it resets today.
        if [ "$(date -d "@${resets_at}" '+%F' 2>/dev/null)" = "$(date '+%F')" ]; then
            when=$(date -d "@${resets_at}" '+%H:%M' 2>/dev/null)
        else
            when=$(date -d "@${resets_at}" '+%a %H:%M' 2>/dev/null)
        fi
        [ -n "$when" ] && text="${text}${RESETS_AT_SEP}${when}"
    fi
    severity "$text" "$pct" "$amber" "$red"
}

# One jq pass rather than seven. Absent fields come back as "-", since an empty
# field would collapse the tab-separated read below.
IFS=$'\t' read -r MODEL DIR CTX FIVE_PCT FIVE_AT SEVEN_PCT SEVEN_AT <<< "$(jq -r '
    [ (.model.display_name // .model.id // "?")
    , (.workspace.current_dir // .cwd // "-")
    , (.context_window.used_percentage // "-")
    , (.rate_limits.five_hour.used_percentage // "-")
    , (.rate_limits.five_hour.resets_at // "-")
    , (.rate_limits.seven_day.used_percentage // "-")
    , (.rate_limits.seven_day.resets_at // "-")
    ] | @tsv' <<< "$INPUT")"

# Turn those "-" placeholders back into empty strings, so every test below is a
# plain "is this present?" check.
for field in DIR CTX FIVE_PCT FIVE_AT SEVEN_PCT SEVEN_AT; do
    [ "${!field}" = "-" ] && printf -v "$field" '%s' ''
done

# The percentages arrive as integers today, but bash's -ge throws on anything
# with a decimal point, which would break the whole line. Truncate defensively.
for field in CTX FIVE_PCT SEVEN_PCT; do
    printf -v "$field" '%s' "${!field%%.*}"
done

LINE="$MODEL"
SEP="${DIM} · ${RESET}"

if [ -n "$DIR" ] && git -C "$DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
    # Detached HEAD has no symbolic ref; fall back to the short sha rather than
    # printing the literal string "HEAD".
    BRANCH=$(git -C "$DIR" symbolic-ref --quiet --short HEAD 2>/dev/null) \
        || BRANCH=$(git -C "$DIR" rev-parse --short HEAD 2>/dev/null)

    if [ -n "$BRANCH" ]; then
        if [ -n "$(git -C "$DIR" status --porcelain --untracked-files=no 2>/dev/null)" ]; then
            BRANCH="${BRANCH}*"
        fi

        # A linked worktree is one whose git dir differs from the repo's common
        # git dir. Testing the paths for "/worktrees/" would also match a repo
        # or branch that merely happens to contain that string.
        GIT_DIR=$(git -C "$DIR" rev-parse --absolute-git-dir 2>/dev/null)
        COMMON_DIR=$(git -C "$DIR" rev-parse --path-format=absolute --git-common-dir 2>/dev/null)
        if [ -n "$GIT_DIR" ] && [ -n "$COMMON_DIR" ] && [ "$GIT_DIR" != "$COMMON_DIR" ]; then
            BRANCH="${BRANCH} ⑂"
        fi

        LINE="${LINE}${SEP}${BRANCH}"
    fi
fi

# Context is roomy on a 1M window, so it earns attention much later than a rate
# limit does.
[ -n "$CTX" ] && LINE="${LINE}${SEP}$(severity "ctx ${CTX}%" "$CTX" 60 85)"

FIVE=$(rate_limit "5h" "$FIVE_PCT" "$FIVE_AT")
[ -n "$FIVE" ] && LINE="${LINE}${SEP}${FIVE}"

SEVEN=$(rate_limit "7d" "$SEVEN_PCT" "$SEVEN_AT")
[ -n "$SEVEN" ] && LINE="${LINE}${SEP}${SEVEN}"

printf '%s\n' "$LINE"

</details>

The fuller version renders as Opus 5 · main · ctx 13% · 5h 96% @18:10 · 7d 77% @Sat 17:00 with some colour-coding and conditional display.

Two caveats, so nobody is misled about scope:

  1. This doesn't close the request. /usage itself still shows no reset time and no live countdown; a status line only redraws on activity, so it's not really a countdown either. Seconding @eltonylfgi-blip that the upstream ask stands.
  2. Only the aggregate windows are exposed. Per-model buckets aren't in the payload — that's #79022 and #84280.

(This comment was drafted by Claude and reviewed by me.)

eltonylfgi-blip · 17 days ago

Useful counterpoint, especially separating the native request from the workaround

One thing would genuinely change what I build next: what made the script the better fit for you? No need to try usage-guard for this. If you happened to look at it, I’d also value whether you would still choose the script and why

Disclosure: I maintain usage-guard

Showing cached comments. Read the full discussion on GitHub ↗