# My Claude Code Status Line: Usage, Pace, and Context at a Glance

**Author:** Mozex | **Published:** 2026-03-22 | **Tags:** Claude Code, Bash, Developer Tools | **URL:** https://mozex.dev/blog/1-my-claude-code-status-line-usage-pace-and-context-at-a-glance

---


I've been using Claude Code as my daily driver for a while now. One thing that kept bugging me was not knowing where I stood with my usage limits until I actually hit them. Claude Code has a status line feature that lets you run a custom script and display whatever you want at the bottom of the terminal. I built one that shows me everything I care about in a single line.

Freek Van der Herten wrote [a nice post](https://freek.dev/3028-adding-a-custom-status-line-to-claude-code) about his own status line showing the repo name and context window percentage. That was my starting point, but I wanted more: rate limit tracking, reset countdowns, and a pace indicator that tells me whether I'm burning through my weekly budget too fast.

<!--more-->

## What It Looks Like

![Claude Code status line showing repository, model, usage percentages, and pace indicator](https://mozex.nbg1.your-objectstorage.com/posts/2026/03/5w4FO4MHsIyeN9aqtvgJCxk7g7VcXj606BHehWdP.webp)

That bottom line is the status bar. Here's what each section means, left to right:

- **`laravel-modules:main`** is the current repo and branch. Useful when you're jumping between projects.
- **`Opus 4.6 (1M context)`** is the active model. I like knowing exactly which model I'm talking to.
- **`5h 5% (2h 16m)`** is my 5-hour rolling usage at 5%, resetting in 2 hours and 16 minutes.
- **`7d 2% (5d 1h)`** is my 7-day usage at 2%, resetting in 5 days and 1 hour.
- **`pace +26%`** is the interesting one. It means I'm 26% *under* my daily budget for the week. Green means I'm pacing well. Red would mean I'm ahead of schedule and might hit the weekly cap.

Colors follow a traffic light pattern: green when usage is below 50%, yellow between 50% and 79%, red at 80% and above.

## How the Pace Indicator Works

This was the part I was most happy with. The idea is simple: if you have a 7-day budget, you should ideally use about 1/7th of it per day. The script figures out which day of the current cycle you're on, calculates what percentage you "should" have used by now, and compares it to your actual usage.

So if it's day 3 of 7, your budget is roughly 43% (3/7). If your actual usage is 17%, you're 26% under budget. That shows up as `pace +26%` in green. If you were at 60% on day 3, you'd see `pace -17%` in red, which is a good signal to ease off before hitting the weekly limit.

## The Script

Drop this into `~/.claude/statusline.sh`:

```bash
#!/bin/bash
input=$(cat)
model=$(echo "$input" | jq -r '.model.display_name // empty' 2>/dev/null || echo "$input" | grep -oP '"display_name":\s*"\K[^"]+' 2>/dev/null | head -1)
repo=$(git remote get-url origin 2>/dev/null | sed 's/.*[:\/]\([^/]*\)\.git$/\1/' | sed 's/.*[:\/]\([^/]*\)$/\1/')
branch=$(git branch --show-current 2>/dev/null || echo 'no-git')

SEP=" · "

RESET="\033[0m"
BOLD="\033[1m"
DIM="\033[2m"
CYAN="\033[36m"
GREEN="\033[32m"
YELLOW="\033[33m"
RED="\033[31m"
MAGENTA="\033[35m"
BLUE="\033[34m"

# Cross-platform helpers: GNU (Linux/Windows Git Bash) then BSD (macOS)
parse_iso_date() {
    date -d "$1" +%s 2>/dev/null || \
    date -jf "%Y-%m-%dT%H:%M:%S" "$(echo "$1" | sed 's/\.[0-9]*Z$//' | sed 's/Z$//')" +%s 2>/dev/null
}

file_mtime() {
    stat -c %Y "$1" 2>/dev/null || stat -f %m "$1" 2>/dev/null || echo 0
}

json_val() {
    jq -r "$1" "$2" 2>/dev/null || grep -oP "$3" "$2" 2>/dev/null
}

ctx_pct=$(echo "$input" | jq -r '.context_window.used_percentage // empty' 2>/dev/null || echo "$input" | grep -oP '"used_percentage":\K[0-9]+' 2>/dev/null | head -1)
ctx_pct=${ctx_pct%.*}

CACHE_FILE="$HOME/.claude/.usage-cache"
CACHE_MAX_AGE=120
now=$(date +%s)
should_refresh=false

if [ ! -f "$CACHE_FILE" ]; then
    should_refresh=true
else
    cache_mtime=$(file_mtime "$CACHE_FILE")
    if [ $((now - cache_mtime)) -ge $CACHE_MAX_AGE ]; then
        should_refresh=true
    fi
fi

if [ "$should_refresh" = true ]; then
    ACCESS_TOKEN=$(jq -r '.claudeAiOauth.accessToken // .accessToken // empty' "$HOME/.claude/.credentials.json" 2>/dev/null || grep -oP '"accessToken":"\K[^"]+' "$HOME/.claude/.credentials.json" 2>/dev/null | head -1)
    if [ -n "$ACCESS_TOKEN" ]; then
        usage=$(curl -s --max-time 3 "https://api.anthropic.com/api/oauth/usage" \
            -H "Accept: application/json" \
            -H "Content-Type: application/json" \
            -H "User-Agent: claude-code/2.1.42" \
            -H "Authorization: Bearer $ACCESS_TOKEN" \
            -H "anthropic-beta: oauth-2025-04-20" 2>/dev/null)
        if echo "$usage" | jq -e '.five_hour' >/dev/null 2>&1 || echo "$usage" | grep -q "five_hour" 2>/dev/null; then
            echo "$usage" > "$CACHE_FILE"
        elif [ -f "$CACHE_FILE" ]; then
            touch "$CACHE_FILE"
        fi
    fi
fi

five_hour=""
seven_day=""
five_reset=""
seven_reset=""
if [ -f "$CACHE_FILE" ]; then
    five_hour=$(json_val '.five_hour.utilization // empty' "$CACHE_FILE" '"five_hour":\{"utilization":\K[0-9.]+')
    seven_day=$(json_val '.seven_day.utilization // empty' "$CACHE_FILE" '"seven_day":\{"utilization":\K[0-9.]+')
    five_reset_raw=$(json_val '.five_hour.resets_at // empty' "$CACHE_FILE" '"five_hour":\{"utilization":[0-9.]+,"resets_at":"\K[^"]+')
    seven_reset_raw=$(json_val '.seven_day.resets_at // empty' "$CACHE_FILE" '"seven_day":\{"utilization":[0-9.]+,"resets_at":"\K[^"]+')

    # Invalidate cached values if the reset time has already passed.
    # After a reset, the old utilization % is guaranteed wrong — the window started over.
    is_past_reset() {
        local reset_epoch
        reset_epoch=$(parse_iso_date "$1")
        [ -n "$reset_epoch" ] && [ "$now" -ge "$reset_epoch" ]
    }

    if [ -n "$five_reset_raw" ] && is_past_reset "$five_reset_raw"; then
        five_hour=""
        five_reset_raw=""
    fi
    if [ -n "$seven_reset_raw" ] && is_past_reset "$seven_reset_raw"; then
        seven_day=""
        seven_reset_raw=""
    fi

    # Also mark stale if cache is older than 5 minutes (API may be failing silently)
    STALE_THRESHOLD=300
    cache_age=$((now - $(file_mtime "$CACHE_FILE")))
    usage_stale=false
    if [ "$cache_age" -ge "$STALE_THRESHOLD" ]; then
        usage_stale=true
    fi

    time_until() {
        local reset_epoch
        reset_epoch=$(parse_iso_date "$1")
        if [ -z "$reset_epoch" ]; then return; fi
        local diff=$(( reset_epoch - now ))
        if [ "$diff" -le 0 ]; then echo "now"; return; fi
        local hours=$(( diff / 3600 ))
        local mins=$(( (diff % 3600) / 60 ))
        if [ "$hours" -gt 24 ]; then
            local days=$(( hours / 24 ))
            hours=$(( hours % 24 ))
            echo "${days}d ${hours}h"
        elif [ "$hours" -gt 0 ]; then
            echo "${hours}h ${mins}m"
        else
            echo "${mins}m"
        fi
    }

    five_reset=$(time_until "$five_reset_raw")
    seven_reset=$(time_until "$seven_reset_raw")

    if [ -n "$seven_reset_raw" ] && [ -n "$seven_day" ]; then
        seven_reset_epoch=$(parse_iso_date "$seven_reset_raw")
        if [ -n "$seven_reset_epoch" ]; then
            remaining_secs=$(( seven_reset_epoch - now ))
            if [ "$remaining_secs" -gt 0 ] && [ "$remaining_secs" -lt 604800 ]; then
                elapsed_secs=$(( 604800 - remaining_secs ))
                current_day=$(( elapsed_secs / 86400 + 1 ))
                [ "$current_day" -gt 7 ] && current_day=7
                budget_pct=$(( current_day * 100 / 7 ))
                budget_diff=$(( budget_pct - ${seven_day%.*} ))
                if [ "$budget_diff" -ge 0 ]; then
                    pace_indicator="${GREEN}+${budget_diff}%${RESET}"
                else
                    pace_indicator="${RED}${budget_diff}%${RESET}"
                fi
            fi
        fi
    fi
fi

five_hour=${five_hour%.*}
seven_day=${seven_day%.*}

color_for_pct() {
    local pct=${1:-0}
    if [ "$pct" -ge 80 ]; then
        echo -e "$RED"
    elif [ "$pct" -ge 50 ]; then
        echo -e "$YELLOW"
    else
        echo -e "$GREEN"
    fi
}

output=""
if [ -n "$repo" ]; then
    output+="${BLUE}${repo}${DIM}:${CYAN}${branch}${RESET}"
    output+="${DIM}${SEP}${RESET}"
fi
output+="${BOLD}${MAGENTA}${model:-Claude}${RESET}"

if [ -n "$five_hour" ] && [ -n "$seven_day" ]; then
    stale_mark=""
    [ "$usage_stale" = true ] && stale_mark="${DIM}?${RESET}"
    five_color=$(color_for_pct "${five_hour:-0}")
    seven_color=$(color_for_pct "${seven_day:-0}")
    output+="${DIM}${SEP}${RESET}"
    output+="${DIM}5h${RESET} ${five_color}${BOLD}${five_hour:-0}%${stale_mark}${RESET}"
    [ -n "$five_reset" ] && output+=" ${CYAN}(${five_reset})${RESET}"
    output+="${DIM}${SEP}${RESET}"
    output+="${DIM}7d${RESET} ${seven_color}${BOLD}${seven_day:-0}%${stale_mark}${RESET}"
    [ -n "$seven_reset" ] && output+=" ${CYAN}(${seven_reset})${RESET}"
    [ -n "$pace_indicator" ] && output+="${DIM}${SEP}${RESET}${DIM}pace${RESET} ${pace_indicator}"
fi

if [ -n "$ctx_pct" ] && [ "$ctx_pct" != "null" ]; then
    ctx_color=$(color_for_pct "${ctx_pct:-0}")
    output+="${DIM}${SEP}${RESET}"
    output+="${DIM}ctx${RESET} ${ctx_color}${BOLD}${ctx_pct}%${RESET}"
fi

printf "%b" "$output"
echo
```

It's about 170 lines. Not tiny, but not bloated either. Every line does something.

## What's Going On in There

The script reads JSON from stdin (Claude Code pipes session data to it), pulls out the model name and context window usage, then does a few things on its own:

**Usage data from the API.** It reads your OAuth access token from `~/.claude/.credentials.json` and hits Anthropic's usage endpoint to get your 5-hour and 7-day utilization percentages. To keep things responsive, it caches the response for 2 minutes in `~/.claude/.usage-cache`. The status line script runs on every render, so without caching you'd be making API calls constantly.

**Reset countdowns.** The API returns ISO 8601 timestamps for when each limit resets. The `time_until` function converts those into human-readable countdowns like `2h 16m` or `5d 1h`.

**Pace calculation.** This divides the 7-day cycle into daily budgets. If you're on day 3, your "expected" usage is 3/7ths of the total. The difference between expected and actual shows whether you're pacing well (green positive number) or burning through your budget too quickly (red negative number).

**Cross-platform helpers.** The three small functions at the top (`parse_iso_date`, `file_mtime`, `json_val`) handle the GNU vs BSD tool differences between Linux/Windows and macOS. Each one tries the GNU syntax first and falls back to the BSD equivalent. On Windows with Git Bash, the GNU versions work. On macOS, the BSD fallbacks kick in. No `if` blocks checking `uname`, just quiet fallthrough.

## Setting It Up

Two steps.

**1. Create the script file.** Save the script above to `~/.claude/statusline.sh` and make it executable:

```bash
chmod +x ~/.claude/statusline.sh
```

On Windows with Git Bash, `chmod` isn't strictly necessary, but it doesn't hurt.

**2. Add it to your Claude Code settings.** Open `~/.claude/settings.json` (create it if it doesn't exist) and add the `statusLine` key:

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

That's it. Next time you start a Claude Code session, the status line appears at the bottom.

## Prerequisites

The script needs `curl` to fetch usage data from the API. You almost certainly have this already.

For JSON parsing, it tries `jq` first and falls back to `grep` with Perl regex. If you have `jq` installed, great. If you don't (Windows Git Bash often doesn't ship with it), the `grep -oP` fallback handles it. On macOS where `grep -oP` doesn't work, `jq` is your best bet. Install it with `brew install jq` if you don't have it.

## What If You Don't Want All of It

The output section at the bottom of the script is where everything comes together. You can comment out or remove any block you don't need. Want just the repo and model without usage tracking? Delete everything from the `CACHE_FILE` line down to the end of the `fi` block, and remove the usage section in the output builder. The sections are independent enough that you can pick what matters to you.

This is a script on your machine, not a package. Make it yours.