See your Claude usage limits in the Claude Code footer
I kept typing /usage in Claude Code to check how close I was to my plan limits. Now the
footer shows it all the time:
- Context is how much of the current conversation’s context window is used.
- Session is your rolling 5-hour plan limit. It counts every Claude Code window, claude.ai and your other devices, not just this conversation.
- Weekly is your 7-day plan limit, with the day and time it resets.
Each bar has 10 blocks of 10%. The filled part changes colour as you use more:
- Up to 50%
- Over 50%
- Over 70%
- Over 80%
How it works
Claude Code can run a script of your choice to draw a
status line above its footer. After every reply
it sends that script some JSON about the session. For Pro and Max subscribers, that JSON
includes rate_limits.five_hour and rate_limits.seven_day, each with a used_percentage
and a resets_at time. The script reads those numbers with jq and prints one line.
Before you start
- You need a claude.ai Pro or Max plan, with Claude Code signed in to that account. With an API key, Session and Weekly don’t appear.
- The script needs
jq:brew install jqon macOS,sudo apt install jqon Debian and Ubuntu.
Install
Pick one way. Both make the same changes, so you can install one way and uninstall the other.
Both back up any status line you already have, as ~/.claude/statusline.sh.bak and
~/.claude/statusline-setting.bak.json. Installing again never overwrites that backup.
Ask Claude Code
Paste this prompt into Claude Code. It shows you each change before making it, checks for
jq, and tests the footer at the end.
Show the install prompt
Install a usage footer for my Claude Code status line. Show me each change before you make it.
1. If ~/.claude/statusline.sh exists and ~/.claude/statusline.sh.bak does not, copy statusline.sh to statusline.sh.bak.
2. If ~/.claude/statusline-setting.bak.json does not exist, save the current "statusLine" value from ~/.claude/settings.json into it as JSON. Write null if there is no statusLine or no settings.json.
3. Write the script below to ~/.claude/statusline.sh, exactly as given.
4. In ~/.claude/settings.json, set "statusLine" to {"type": "command", "command": "bash ~/.claude/statusline.sh"}. Create the file if it is missing. Keep every other setting.
5. Check that jq is installed. If it is not, tell me the command to install it on my system.
6. Test the script by piping this sample JSON into it, and show me the output:
{"model":{"display_name":"Opus 5.5"},"context_window":{"used_percentage":7},"rate_limits":{"five_hour":{"used_percentage":37},"seven_day":{"used_percentage":72}}}
```bash
#!/bin/bash
# Claude Code status line: model | context | 5h session limit | 7d weekly limit, each with a bar
input=$(cat)
B=$'\033[1m'; N=$'\033[22m'
# 10-block bar: filled part on a solid dark gray track, so it reads as progress.
# Fill is light gray, then yellow above 50%, orange above 70%, red above 80%.
# Leftover under 10% becomes a partial block (eighths) drawn over the track.
T=$'\033[38;5;238m'; R=$'\033[39;49m'
bar() {
local p=$1 fill='' track='' edge='' i c
[ "$p" -gt 100 ] && p=100
if [ "$p" -gt 80 ]; then c=196; elif [ "$p" -gt 70 ]; then c=208; elif [ "$p" -gt 50 ]; then c=220; else c=250; fi
local F=$'\033[38;5;'"${c}m" P=$'\033[38;5;'"${c};48;5;238m"
local filled=$((p / 10)) part=$(( (p % 10) * 8 / 10 ))
[ $part -eq 0 ] && [ $((p % 10)) -gt 0 ] && part=1
local partials=('' '▏' '▎' '▍' '▌' '▋' '▊' '▉')
for ((i = 0; i < filled; i++)); do fill="${fill}█"; done
local rest=$((10 - filled))
if [ $part -gt 0 ]; then edge="${P}${partials[$part]}${R}"; rest=$((rest - 1)); fi
for ((i = 0; i < rest; i++)); do track="${track}█"; done
printf '%s%s%s%s%s%s%s %s%s%%%s' "$F" "$fill" "$R" "$edge" "$T" "$track" "$R" "$B" "$p" "$N"
}
# Epoch seconds to local time; date -r is macOS, date -d @ is Linux
fmt() { date -r "$1" "$2" 2>/dev/null || date -d "@$1" "$2" 2>/dev/null; }
model=$(echo "$input" | jq -r '.model.display_name')
ctx=$(printf '%.0f' "$(echo "$input" | jq -r '.context_window.used_percentage // 0')")
# Rate limits (Claude.ai subscribers only; absent until first API response)
five=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')
week=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty')
five_reset=$(echo "$input" | jq -r '.rate_limits.five_hour.resets_at // empty')
week_reset=$(echo "$input" | jq -r '.rate_limits.seven_day.resets_at // empty')
line="$model | Context: $(bar "$ctx")"
if [ -n "$five" ]; then
line="$line | Session: $(bar "$(printf '%.0f' "$five")")"
[ -n "$five_reset" ] && line="$line (resets $(fmt "$five_reset" +%H:%M))"
fi
if [ -n "$week" ]; then
line="$line | Weekly: $(bar "$(printf '%.0f' "$week")")"
[ -n "$week_reset" ] && line="$line (resets $(fmt "$week_reset" '+%a %H:%M'))"
fi
printf '%s' "$line"
```
Or paste a terminal command
This writes the script to ~/.claude/statusline.sh and points statusLine in
~/.claude/settings.json at it. Your other settings stay as they are.
Show the install command
mkdir -p ~/.claude
f=~/.claude/settings.json; [ -f "$f" ] || echo '{}' > "$f"
# Back up your current status line once (a second install will not overwrite the backup)
[ -f ~/.claude/statusline.sh ] && [ ! -f ~/.claude/statusline.sh.bak ] && cp ~/.claude/statusline.sh ~/.claude/statusline.sh.bak
[ -f ~/.claude/statusline-setting.bak.json ] || jq '.statusLine // null' "$f" > ~/.claude/statusline-setting.bak.json
cat > ~/.claude/statusline.sh <<'EOF'
#!/bin/bash
# Claude Code status line: model | context | 5h session limit | 7d weekly limit, each with a bar
input=$(cat)
B=$'\033[1m'; N=$'\033[22m'
# 10-block bar: filled part on a solid dark gray track, so it reads as progress.
# Fill is light gray, then yellow above 50%, orange above 70%, red above 80%.
# Leftover under 10% becomes a partial block (eighths) drawn over the track.
T=$'\033[38;5;238m'; R=$'\033[39;49m'
bar() {
local p=$1 fill='' track='' edge='' i c
[ "$p" -gt 100 ] && p=100
if [ "$p" -gt 80 ]; then c=196; elif [ "$p" -gt 70 ]; then c=208; elif [ "$p" -gt 50 ]; then c=220; else c=250; fi
local F=$'\033[38;5;'"${c}m" P=$'\033[38;5;'"${c};48;5;238m"
local filled=$((p / 10)) part=$(( (p % 10) * 8 / 10 ))
[ $part -eq 0 ] && [ $((p % 10)) -gt 0 ] && part=1
local partials=('' '▏' '▎' '▍' '▌' '▋' '▊' '▉')
for ((i = 0; i < filled; i++)); do fill="${fill}█"; done
local rest=$((10 - filled))
if [ $part -gt 0 ]; then edge="${P}${partials[$part]}${R}"; rest=$((rest - 1)); fi
for ((i = 0; i < rest; i++)); do track="${track}█"; done
printf '%s%s%s%s%s%s%s %s%s%%%s' "$F" "$fill" "$R" "$edge" "$T" "$track" "$R" "$B" "$p" "$N"
}
# Epoch seconds to local time; date -r is macOS, date -d @ is Linux
fmt() { date -r "$1" "$2" 2>/dev/null || date -d "@$1" "$2" 2>/dev/null; }
model=$(echo "$input" | jq -r '.model.display_name')
ctx=$(printf '%.0f' "$(echo "$input" | jq -r '.context_window.used_percentage // 0')")
# Rate limits (Claude.ai subscribers only; absent until first API response)
five=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')
week=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty')
five_reset=$(echo "$input" | jq -r '.rate_limits.five_hour.resets_at // empty')
week_reset=$(echo "$input" | jq -r '.rate_limits.seven_day.resets_at // empty')
line="$model | Context: $(bar "$ctx")"
if [ -n "$five" ]; then
line="$line | Session: $(bar "$(printf '%.0f' "$five")")"
[ -n "$five_reset" ] && line="$line (resets $(fmt "$five_reset" +%H:%M))"
fi
if [ -n "$week" ]; then
line="$line | Weekly: $(bar "$(printf '%.0f' "$week")")"
[ -n "$week_reset" ] && line="$line (resets $(fmt "$week_reset" '+%a %H:%M'))"
fi
printf '%s' "$line"
EOF
jq '.statusLine = {"type":"command","command":"bash ~/.claude/statusline.sh"}' "$f" > "$f.tmp" && mv "$f.tmp" "$f"
Then send any message in Claude Code. The footer fills in after the first reply, because that reply carries the usage numbers.
Uninstall
This puts back the status line you had before, or removes it if you had none.
Paste this into Claude Code:
Remove the usage footer from my Claude Code status line and put back what I had before. Show me each change before you make it.
1. If ~/.claude/statusline.sh.bak exists, move it back to ~/.claude/statusline.sh. If it does not exist, delete ~/.claude/statusline.sh.
2. If ~/.claude/statusline-setting.bak.json exists, read its value. If the value is null, remove "statusLine" from ~/.claude/settings.json. Otherwise set "statusLine" to that value. Then delete statusline-setting.bak.json.
3. If ~/.claude/statusline-setting.bak.json does not exist, remove "statusLine" from ~/.claude/settings.json.
4. Keep every other setting in settings.json.
Or run this in your terminal:
f=~/.claude/settings.json; b=~/.claude/statusline-setting.bak.json
# Put back your old script, or remove this one if you had none
if [ -f ~/.claude/statusline.sh.bak ]; then mv ~/.claude/statusline.sh.bak ~/.claude/statusline.sh; else rm -f ~/.claude/statusline.sh; fi
# Put back your old statusLine setting, or remove it if you had none
if [ -f "$b" ]; then
jq --slurpfile p "$b" 'if $p[0] == null then del(.statusLine) else .statusLine = $p[0] end' "$f" > "$f.tmp" && mv "$f.tmp" "$f" && rm "$b"
else
jq 'del(.statusLine)' "$f" > "$f.tmp" && mv "$f.tmp" "$f"
fi
Good to know
- The footer can trail
/usageby a point or two. It only gets new numbers when a reply arrives in that window, while your other windows keep using the same limit. - It works on macOS and Linux. Reset times use your computer’s time zone.