trailofbits / trailofbits/claude-code-config

Enhancement: Add account usage limits to statusline (session %, 7-day %, Sonnet %)

Open
#32 1 comment 3 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Shell
Stars
2.1k
Forks
161
Avg merge
3d 59m
Merged PRs (30d)
1

Description

Summary

The current scripts/statusline.sh shows context window usage, cost, and session duration. This proposal adds a third line displaying account-level rate limit usage — session (5-hour), weekly (all models), and weekly (Sonnet-only) — fetched from Anthropic's OAuth usage API with local caching.

Problem

Heavy Claude Code users frequently hit the 5-hour session limit or weekly limits without any visible warning until they get a hard block. The existing statusline has no visibility into these limits.

Proposed Solution

Add a Line 3 to the statusline that shows account usage limits in real time:

[Sonnet 4.6] 📁 my-project │ 🌿 main

████████░░░░ 42% (84k/200k) │ $0.00 │ ⏱ 12m 30s │ ↻78%

Session: 42% ⟳2h18m │ 7d all: 15% ⟳Sat 16h │ Sonnet: 18% ⟳Sat 16h

Color-coded with the same thresholds as the existing context bar:

  • 🟢 Green: < 50%
  • 🟡 Yellow: 50–79%
  • 🔴 Red: ≥ 80%

API

The data comes from an undocumented but stable Anthropic OAuth endpoint:

GET https://api.anthropic.com/api/oauth/usage
Authorization: Bearer <oauth-access-token>
anthropic-version: 2023-06-01
anthropic-beta: oauth-2025-04-20

Response:

{
  "five_hour": {
    "utilization": 42.0,
    "resets_at": "2026-02-22T00:00:00.318160+00:00"
  },
  "seven_day": {
    "utilization": 15.0,
    "resets_at": "2026-02-28T19:00:00.318184+00:00"
  },
  "seven_day_sonnet": {
    "utilization": 18.0,
    "resets_at": "2026-02-28T19:00:00.318198+00:00"
  },
  "seven_day_opus": null,
  "extra_usage": {
    "is_enabled": false,
    "monthly_limit": null
  }
}

The token is read directly from ~/.claude/.credentials.json (the same file Claude Code already uses), so no additional configuration is needed.

Implementation Details

Caching strategy

The statusline runs on every Claude Code render cycle. To avoid hammering the API, usage data is cached in ~/.claude/usage-cache.json with a 2-minute TTL:

USAGE_CACHE="$HOME/.claude/usage-cache.json"
CACHE_TTL=120

fetch_usage() {
    local now
    now=$(date +%s)

    if [[ -f "$USAGE_CACHE" ]]; then
        local cache_time
        cache_time=$(jq -r '.cached_at // 0' "$USAGE_CACHE" 2>/dev/null)
        if [[ $((now - cache_time)) -lt $CACHE_TTL ]]; then
            cat "$USAGE_CACHE"
            return
        fi
    fi

    local token
    token=$(jq -r '.claudeAiOauth.accessToken // empty' \
        "$HOME/.claude/.credentials.json" 2>/dev/null)
    [[ -z "$token" ]] && { [[ -f "$USAGE_CACHE" ]] && cat "$USAGE_CACHE" || echo "{}"; return; }

    local response
    response=$(curl -sf --max-time 4 \
        -H "Authorization: Bearer $token" \
        -H "Content-Type: application/json" \
        -H "anthropic-version: 2023-06-01" \
        -H "anthropic-beta: oauth-2025-04-20" \
        "https://api.anthropic.com/api/oauth/usage" 2>/dev/null)

    if [[ -n "$response" ]] && echo "$response" | jq -e '.five_hour' &>/dev/null; then
        echo "$response" | jq ". + {cached_at: $now}" > "$USAGE_CACHE" 2>/dev/null
        echo "$response"
    else
        # Serve stale cache on failure rather than breaking the statusline
        [[ -f "$USAGE_CACHE" ]] && cat "$USAGE_CACHE" || echo "{}"
    fi
}
Reset time formatting
format_reset() {
    local iso="$1"
    [[ -z "$iso" || "$iso" == "null" ]] && echo "?" && return

    local reset_epoch now_epoch diff
    reset_epoch=$(date -d "$iso" +%s 2>/dev/null) || { echo "?"; return; }
    now_epoch=$(date +%s)
    diff=$(( reset_epoch - now_epoch ))

    if [[ $diff -le 0 ]]; then
        echo "now"
    elif [[ $diff -lt 3600 ]]; then
        printf "%dm" $(( diff / 60 ))
    elif [[ $diff -lt 86400 ]]; then
        printf "%dh%dm" $(( diff / 3600 )) $(( (diff % 3600) / 60 ))
    else
        LC_TIME=C date -d "$iso" +"%a %Hh" 2>/dev/null || echo "?"
    fi
}
Line 3 assembly
if [ -n "$sess_pct" ] && [ "$sess_pct" != "null" ]; then
    if   [ "$sess_pct" -lt 50 ]; then sess_color='\033[32m'
    elif [ "$sess_pct" -lt 80 ]; then sess_color='\033[33m'
    else                               sess_color='\033[31m'; fi

    sess_reset=$(format_reset "$sess_reset_at")
    line3="$(printf '\033[2mSession:\033[0m %b%s%%\033[0m \033[2m⟳%s\033[0m' \
        "$sess_color" "$sess_pct" "$sess_reset")"

    # 7-day all models
    if [ -n "$week_pct" ] && [ "$week_pct" != "null" ]; then
        if   [ "$week_pct" -lt 50 ]; then week_color='\033[32m'
        elif [ "$week_pct" -lt 80 ]; then week_color='\033[33m'
        else                               week_color='\033[31m'; fi

        week_reset=$(format_reset "$week_reset_at")
        line3="$line3 $(printf '%b \033[2m7d all:\033[0m %b%s%%\033[0m \033[2m⟳%s\033[0m' \
            "$SEP" "$week_color" "$week_pct" "$week_reset")"
    fi

    # 7-day Sonnet-only (only shown when non-null)
    if [ -n "$sonnet_pct" ] && [ "$sonnet_pct" != "null" ]; then
        if   [ "$sonnet_pct" -lt 50 ]; then son_color='\033[32m'
        elif [ "$sonnet_pct" -lt 80 ]; then son_color='\033[33m'
        else                                 son_color='\033[31m'; fi

        line3="$line3 $(printf '%b \033[2mSonnet:\033[0m %b%s%%\033[0m \033[2m⟳%s\033[0m' \
            "$SEP" "$son_color" "$sonnet_pct" "$(format_reset "$sonnet_reset_at")")"
    fi
fi

Failure modes

Scenario Behavior
No credentials file Line 3 omitted entirely, Lines 1–2 unaffected
API unreachable / timeout (4s max) Serves stale cache; if no cache, Line 3 omitted
API returns error JSON Stale cache used; graceful fallback to no Line 3
seven_day_sonnet is null (Opus plan) Sonnet segment omitted from Line 3
jq not installed No Line 3; existing behavior preserved

Token count display (bonus)

While at it, Line 2 also gains an absolute token count display — useful alongside the percentage for planning context compaction:

████████░░░░ 42% (84k/200k) │ $0.00 │ ⏱ 12m 30s

Related

Happy to submit a PR if this direction makes sense to the maintainers.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with scripts/statusline.sh and trace how the existing context, cost, and duration lines are assembled. Review the proposed credentials, cache, curl, jq, and reset-formatting paths, then run the statusline with and without credentials and with an unavailable API. Done means account usage appears when available while missing credentials, failures, null Sonnet data, and missing jq preserve the existing lines.

Written by the indexing model from the issue text.

Assessment

Tech stack
shell
Domain
cli, tooling
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.