유틸

Claude Code 사용량(rate limit)을 statusline에 상시 표시하기 — 과속 감지 바 만들기

warpmemory 2026. 7. 30. 12:17

들어가며: 한도는 소진되고 나서야 보인다

Claude Code 구독 요금제에는 두 종류의 사용 한도가 있습니다. 하나는 5시간 롤링 윈도우, 다른 하나는 주간(7일) 한도입니다. 문제는 이 남은 양을 확인하는 내장 방법인 /usage 명령이 대화형 세션에서 내가 직접 쳐야만 보인다는 것입니다.

작업에 몰입하다 보면 정작 한도가 얼마나 남았는지 신경 쓸 겨를이 없습니다. 그러다 갑자기 "한도에 도달했다"는 메시지를 만나면, 흐름이 뚝 끊깁니다. 그래서 매 순간 시야 안에 남은 한도가 들어와 있으면 좋겠다는 생각으로 statusline 스크립트를 만들었습니다.

아이디어: 바 두 개를 나란히, 그리고 "과속 감지"

statusline에 두 개의 막대를 나란히 그립니다.

  • 사용률 바 — 지금까지 몇 %를 썼는가. 색으로 위험도를 표현합니다(초록 <70%, 노랑 <90%, 빨강 ≥90%).
  • 윈도우 경과 바 — 현재 윈도우에서 시간이 얼마나 흘렀는가(파랑 ⏱).

여기서 이 스크립트의 핵심 아이디어가 나옵니다. 두 바를 같은 방향(경과율)으로 맞춰 놓으면, 두 값을 비교하는 것만으로 "과속" 여부를 한눈에 감지할 수 있다는 점입니다.

생각해 보면 간단합니다. 5시간 동안 100%를 균등하게 쓴다면, 시간이 절반(50%) 지났을 때 사용률도 대략 50%여야 합니다. 그런데 만약 사용률 > 경과율이라면? 지금 페이스라면 리셋 시각이 오기 전에 한도를 먼저 소진한다는 뜻입니다. 즉 "과속" 중입니다. 반대로 사용률 < 경과율이면 여유가 있습니다.

숫자 두 개를 눈으로 빼는 계산 없이, 사용률 바가 시간 바보다 길면 아껴 쓰라는 신호. 이게 이 스크립트를 오래 쓰게 만든 킬러 포인트입니다.

동작 원리: statusline은 stdin으로 JSON을 받는다

Claude Code의 statusline은 settings.json에 커맨드를 등록하는 방식입니다. 등록된 커맨드는 실행될 때 stdin으로 세션 상태 JSON을 전달받고, 표준출력으로 뱉은 한 줄이 statusline에 그대로 표시됩니다.

이 JSON 안에는 구독 계정 한정으로 rate_limits 필드가 들어 있습니다. 구조는 대략 이렇습니다.

{
  "model": { "display_name": "Opus 4.8" },
  "rate_limits": {
    "five_hour": { "used_percentage": 52.0, "resets_at": 1753859000 },
    "seven_day": { "used_percentage": 38.0, "resets_at": 1754179740 }
  }
}

used_percentage는 사용률(%), resets_at은 한도가 초기화되는 시각(epoch 초)입니다. 이 두 값만 있으면 사용률 바와 경과 바를 모두 그릴 수 있습니다. 필드 스펙은 공식 statusline 문서에 정리되어 있습니다.

구현: 스크립트 전문

~/.claude/statusline.sh에 저장할 스크립트 전체입니다.

#!/bin/bash
# Claude Code statusline — 모델명 + 5시간/주간 사용한도 바 그래프
# 사용률 바(초록<70 노랑<90 빨강≥90) + 남은시간 바(파랑) + 리셋 시각
# stdin JSON의 rate_limits 필드 사용 (구독 계정, 첫 API 응답 이후에만 존재)
export PATH="/opt/homebrew/bin:$PATH"

input=$(cat)

MODEL=$(echo "$input" | jq -r '.model.display_name // .model.id // "?"')
NOW=$(date +%s)

RESET=$'\033[0m'; GREEN=$'\033[32m'; YELLOW=$'\033[33m'; RED=$'\033[31m'
BLUE=$'\033[36m'; DIM=$'\033[2m'

bar() {  # $1=percent(0~100 정수) $2=width → █/░ 바 문자열
  local pct=$1 width=$2 filled
  filled=$(( (pct * width + 50) / 100 ))
  [ "$filled" -gt "$width" ] && filled=$width
  [ "$filled" -lt 0 ] && filled=0
  local i out=""
  for ((i=0; i<filled; i++)); do out+="█"; done
  for ((i=filled; i<width; i++)); do out+="░"; done
  printf '%s' "$out"
}

fmt_window() {  # $1=used_pct $2=resets_at $3=라벨 $4=윈도우초
  local pct="$1" reset_at="$2" label="$3" win="$4"
  [ -z "$pct" ] && return
  local ipct; ipct=$(printf '%.0f' "$pct")

  local color=$GREEN
  [ "$ipct" -ge 70 ] && color=$YELLOW
  [ "$ipct" -ge 90 ] && color=$RED

  # 윈도우 경과 시간 % 와 리셋 시각 (사용률과 같은 방향 — 경과%보다 사용률이 크면 과속)
  local time_part=""
  if [ -n "$reset_at" ] && [ "$reset_at" != "null" ]; then
    local remain=$(( reset_at - NOW ))
    [ "$remain" -lt 0 ] && remain=0
    [ "$remain" -gt "$win" ] && remain=$win
    local rpct=$(( (win - remain) * 100 / win ))
    local rst_str
    if [ "$win" -gt 86400 ]; then
      rst_str=$(date -r "$reset_at" '+%m/%d %H:%M' 2>/dev/null)
    else
      rst_str=$(date -r "$reset_at" '+%H:%M' 2>/dev/null)
    fi
    time_part=$(printf ' %s⏱%s %s%%%s %s→%s%s' \
      "$BLUE" "$(bar "$rpct" 6)" "$rpct" "$RESET" "$DIM" "$rst_str" "$RESET")
  fi

  printf '%s %s%s %s%%%s%s' \
    "$label" "$color" "$(bar "$ipct" 6)" "$ipct" "$RESET" "$time_part"
}

FIVE_PCT=$(echo "$input" | jq -r '.rate_limits.five_hour.used_percentage // empty')
FIVE_RST=$(echo "$input" | jq -r '.rate_limits.five_hour.resets_at // empty')
WEEK_PCT=$(echo "$input" | jq -r '.rate_limits.seven_day.used_percentage // empty')
WEEK_RST=$(echo "$input" | jq -r '.rate_limits.seven_day.resets_at // empty')

OUT="[$MODEL]"
F=$(fmt_window "$FIVE_PCT" "$FIVE_RST" "5h" $((5*3600)))
W=$(fmt_window "$WEEK_PCT" "$WEEK_RST" "7d" $((7*86400)))
[ -n "$F" ] && OUT="$OUT $F"
[ -n "$W" ] && OUT="$OUT ${DIM}|${RESET} $W"
[ -z "$F$W" ] && OUT="$OUT ${DIM}한도정보 대기중${RESET}"

echo "$OUT"

핵심 해설 ① bar() — 정수 산술로 그리는 막대

bar()는 퍼센트와 너비를 받아 (채움)과 (빈칸)로 이루어진 막대 문자열을 반환합니다. 채울 칸 수 계산에서 눈여겨볼 부분은 (pct * width + 50) / 100입니다. bash 산술은 정수 나눗셈만 하므로 그냥 나누면 항상 버림이 됩니다. 여기에 + 50(즉 100의 절반)을 더해 반올림 효과를 냅니다. 이렇게 하면 51%가 6칸짜리 바에서 3칸으로 자연스럽게 반올림됩니다.

핵심 해설 ② fmt_window() — 경과율 계산

과속 감지의 심장부입니다. resets_at(리셋 epoch)에서 현재 시각 NOW를 빼면 남은 시간(remain)이 나옵니다. 윈도우 전체 길이 win(5시간이면 18000초, 7일이면 604800초)에서 이 남은 시간을 빼면 이미 흐른 시간이고, 그것을 %로 환산한 것이 경과율 rpct입니다.

local remain=$(( reset_at - NOW ))
local rpct=$(( (win - remain) * 100 / win ))

rpct로 파랑 ⏱ 바를 그립니다. 그래서 사용률 색상 바와 시간 바가 같은 축(0~100%) 위에서 비교되고, 앞에서 말한 "사용률 > 경과율 = 과속" 판단이 눈으로 바로 됩니다.

설치

  1. 위 스크립트를 ~/.claude/statusline.sh로 저장합니다.
  2. 실행 권한을 줍니다.
    chmod +x ~/.claude/statusline.sh
  3. ~/.claude/settings.json에 statusLine을 등록합니다.
    {
      "statusLine": {
        "type": "command",
        "command": "~/.claude/statusline.sh",
        "refreshInterval": 30
      }
    }

등록은 다음 세션부터 적용될 수 있습니다. 이미 켜져 있는 세션이라면 새로 시작해 보세요. 참고로 스크립트는 jq를 사용하므로 없다면 먼저 설치해야 합니다.

함정 노트: 만들며 실제로 걸린 것들

1) rate_limits는 첫 API 응답 이후에만 온다

세션을 막 시작한 직후에는 stdin JSON에 rate_limits 필드가 아직 없습니다. 이 필드는 그 세션에서 첫 API 응답을 받은 뒤부터 채워집니다. 그래서 스크립트 마지막에 두 값이 모두 비어 있으면 한도정보 대기중이라는 폴백 문구를 출력하도록 해 두었습니다. 이게 없으면 세션 초반에 statusline이 텅 비어 당황하게 됩니다.

2) ⏱ 바를 실시간으로 흐르게 하려면 refreshInterval이 필요하다

statusline의 기본 갱신은 이벤트 기반입니다(응답 도착 등, 300ms 디바운스). 즉 아무 일도 안 일어나는 유휴 상태에서는 statusline이 멈춰 있습니다. 그러면 시간이 흐르는데도 ⏱ 경과 바가 갱신되지 않습니다. 이걸 실시간으로 흐르게 하려고 "refreshInterval": 30(30초마다 재실행)을 넣었습니다.

다만 주의할 점: 사용률 %는 API 응답이 있어야만 갱신됩니다. 유휴 중에는 시간 바(⏱)만 흐르고, 사용률 바는 마지막으로 받은 값에 머물러 있습니다. 이건 데이터의 성질상 어쩔 수 없는 부분입니다.

3) date -r은 macOS 전용 — 리눅스는 -d @

스크립트의 리셋 시각 포맷팅에 쓴 date -r "$reset_at"macOS(BSD date) 전용 문법입니다. 리눅스(GNU date)에서는 동작하지 않으니 다음처럼 바꿔야 합니다.

OS epoch → 시각 포맷
macOS (BSD) date -r "$reset_at" '+%H:%M'
Linux (GNU) date -d @"$reset_at" '+%H:%M'

마무리: 결과 화면

완성된 statusline은 이런 한 줄로 표시됩니다(색상은 텍스트로 재현 불가하니 상상해 주세요).

[Opus 4.8] 5h ███░░░ 52% ⏱██░░░░ 31% →16:40 | 7d ██░░░░ 38% ⏱███░░░ 55% →08/03 09:00

읽는 법:

  • 5h: 사용률 52% > 경과 31% → 과속 중. 리셋(16:40)이 오기 전에 5시간 한도를 먼저 소진할 페이스입니다. 잠깐 속도를 늦출 신호.
  • 7d: 사용률 38% < 경과 55% → 여유. 주간 한도는 시간 대비 덜 쓰고 있으니 걱정 없습니다.

단순히 "몇 % 썼다"를 넘어서, 지금 페이스가 지속 가능한지를 두 바의 길이 비교만으로 알려 준다는 점이 이 작은 스크립트의 가장 큰 효용이었습니다. 한도에 부딪혀 흐름이 끊기는 일이 눈에 띄게 줄었습니다. 터미널 한 줄에 넣어 두기 딱 좋은 투자입니다.