본문으로 건너뛰기

© 2026 Molayo

Zenn헤드라인2026. 06. 19. 21:27

Claude Code의 비용과 플랜 한도를 상시 시각화하기 ― statusline 구현

요약

Claude Code 사용 시 비용과 플랜 한도를 실시간으로 모니터링할 수 있는 statusline 구현 방법을 소개합니다. stdin으로 전달되는 JSON 데이터를 활용해 컨텍스트 사용률, 5시간/7일 한도, 누적 비용을 터미널에 상시 시각화합니다.

핵심 포인트

  • Claude Code의 statusline 스크립트를 통한 실시간 데이터 시각화
  • 5시간/7일 플랜 한도 및 컨텍스트 사용률 모니터링 방법
  • 세션 비용 및 당일 누적액(엔화 환산) 표시 구현
  • jq를 활용한 stdin JSON 데이터 파싱 및 에러 처리

지난번 「Claude Code가 매일 아침 GitHub를 순회하게 만들기」에서, 아침 브리핑에 OSS 발견을 함께 포함시키는 이야기에 대해 썼다. 이번에는 그 이면에서 autopilot이나 Scout를 중단할지 판단하는 데 사용하고 있는 statusline의 비용·플랜 한도 표시를 통째로 해설한다.

인증도 엔드포인트 호출도 필요 없다. Claude Code가 statusline 스크립트로 stdin을 통해 전달하는 JSON을 jq로 읽기만 하면, 5h/7d 플랜 한도·컨텍스트 (Context) 사용률·세션 비용 및 당일 누적액(엔화 환산)이 3줄로 상시 보인다.

고민거리: 소비량이 「나중에 깜짝 놀라는」 방식

Claude Max의 API에는 소프트 레이트 제한 (Soft Rate Limit)이 있다. 5시간 블록과 7일 윈도우(Window) 내에서 출력 토큰이 상한에 가까워지면 모델의 동작이 변하고, 다 써버리면 이후의 슬롯이 skip된다. 문제는 이 소비 상황이 평소에는 보이지 않는다는 것이다.

  • 「왠지 모델의 답변이 부실해졌나?」 → 사실은 5h 한도가 차 있었음
  • autopilot이 백그라운드에서 잡아먹은 분량까지 합산된다는 것을 모른 채 수동 세션을 시작함
  • 비용 감각이 어긋나서, 월말에 ccusage를 보고 나서야 겨우 사태를 파악함

「별도 탭으로 대시보드를 열어두는」 운용은 사흘이면 사라진다. 작업 중인 화면에 항상 숫자가 나와 있으면, 의식하지 않아도 잔량이 눈에 들어온다.

stdin에서 직접 가져올 수 있는 값

Claude Code는 statusline 스크립트를 실행할 때마다 stdin으로 JSON을 흘려보낸다. 그곳에 필요한 값이 거의 다 갖춰져 있다.

# ~/.claude/scripts/statusline.sh
INPUT="$(cat 2>/dev/null || echo '{}')"
j() { printf '%s' "$INPUT" | jq -r "$1" 2>/dev/null; }
...

.context_window.used_percentage는 Claude Code 측에서 사전에 계산하여 전달해 주는 값이므로, 직접 토큰을 셀 필요는 없다. rate_limits는 구독 계약 사용자가 첫 번째 API 응답을 받은 후에만 나타나기 때문에, // empty로 폴백(Fallback) 처리를 해두지 않으면 세션 시작 직후에 에러가 발생한다.

리셋 시각은 epoch 초 단위로 오기 때문에 date로 로컬 시각으로 변환한다.

clk() { [ -n "$1" ] && date -r "$1" "+%H:%M" 2>/dev/null; } # → HH:MM
mdhm() { [ -n "$1" ] && date -r "$1" "+%-m/%-d %H:%M" 2>/dev/null; } # → M/D HH:MM

3줄 레이아웃

출력은 고정된 3줄 구성이다.

line1: 📁 dir ⌥ branch
line2: 🤖 model·effort 🧠 ctx% 🕐 5h N% ⏪HH:MM 📅 7d N% ⏪M/D HH:MM
line3: 💴 session ≈¥x 今日 ≈¥y <health>

실제 포맷 호출은 다음과 같다.

L2=$(printf "${MAG}🤖 %s%s${R} ${DIM}·${R} ${CTXC}🧠 ctx %s${R} ${DIM}·${R} ${C5}🕐 5h %s${R}%b ${DIM}·${R} ${C7}📅 7d %s${R}%b" \
"$MODEL" "$EFF" "$(pct "$CTX")" "$(pct "$H5")" "$R5" "$(pct "$D7")" "$R7")
L3=$(printf "${DIM}💴 session${R} %s ${DIM}今日${R} %s %s%b" \
...

퍼센티지에는 색상이 지정되어 있어, 숫자를 읽지 않아도 상태를 알 수 있다.

# pct -> color (green <50, yellow 50-79, red >=80)
pcolor() { local n="${1%%.*}"; [ -z "$n" ] && { printf '%s' "$DIM"; return; }
if [ "$n" -ge 80 ] 2>/dev/null; then printf '%s' "$RED"
...

당일 비용을 2분 캐시로 가져오기

SESSION_USD

는 stdin으로부터 직접 실시간으로 들어오지만, 「오늘의 총 비용(Total Cost)」은 ccusage의 일일 집계가 필요하다. 매번 호출하면 statusline의 렌더링이 지연되므로, 2분 캐시(Cache) + 백그라운드 업데이트 방식을 사용하고 있다.

CCUSAGE=$(command -v ccusage 2>/dev/null || echo "$HOME/.nvm/versions/node/v24.13.0/bin/ccusage")
DAY_CACHE="/tmp/cc-daily-cache.json"
NEED=true
...

포인트는 &를 사용하여 서브쉘(Subshell)로 던지는 점이다. 캐시가 만료되었더라도 statusline은 그 순간에 이전 값을 반환한 뒤, 백그라운드에서 ccusage를 실행한다. 2분 정도 오래된 수치라도 실제 운영상에는 지장이 없다.

엔화 환산은 서두에서 설정한 환율을 사용하는 awk 한 줄이다.

JPY_RATE=150 # 취향에 맞게 수정 / $를 사용해도 됨.
yen() { awk -v u="$1" -v r="$JPY_RATE" 'BEGIN{
v=u*r; n=sprintf("%.0f", v); s=""; len=length(n); c=0
...

3자리 콤마 구분 형식으로 ¥1,234와 같이 표시한다. 달러 표시를 선호한다면 yen을 호출하는 부분을 그대로 $SESSION_USD로 바꾸기만 하면 된다.

헬스(Health) 1글자 (5분 캐시)

L3의 끝에 🟢/🟡/🔴/⚫ 1글자로 automation의 생사(Liveness)를 표시하고 있다. automation-health.sh의 실행 비용이 높기 때문에, 이 부분은 5분 캐시를 적용한다.

HEALTH_CACHE="/tmp/cc-health-cache"; HEALTH=""
if [ -f "$HEALTH_CACHE" ]; then
HAGE=$(($(date +%s) - $(stat -f %m "$HEALTH_CACHE" 2>/dev/null || echo 0)))
...

캐시가 만료되었을 때는 · (도트)를 반환하면서 백그라운드에서 업데이트를 시작한다. 다음 렌더링 사이클에서 캐시가 완성되면 이모지로 전환된다. statusline의 렌더링은 어떤 경우에도 블로킹(Blocking)하지 않는다는 것이 설계의 핵심이다.

token-budget-advisor.sh: 정밀한 교차 검증

statusline의 표시는 「지금 어느 정도인가」를 시각화하기 위한 것이다. autopilot이 「실행할지·멈출지」를 판단하려면 정밀도가 필요하며, 그 역할은 token-budget-advisor.sh가 담당한다.

ccusage의 공식 블록 집계를 우선시하면서, 독자적인 ~/.claude/logs/cost-log.jsonl 집계와 양쪽 모두 계산하여 차이율(source_diff_pct)을 산출한다.

# ccusage가 있으면 5h 블록의 output token을 가져옴 (transcript 계산보다 공식적임)
if command -v ccusage >/dev/null 2>&1; then
CC_JSON=$(ccusage blocks --json 2>/dev/null || true)
...

판단 임계값(Threshold)은 다음과 같이 설정되어 있다.

THRESH_5H_WARN = 800_000 # output tokens → warn
THRESH_5H_CRIT = 1_200_000 # → critical
THRESH_WEEK_WARN = 3000 # USD / 7d
...

--short 모드에서는 한 줄로 출력되며, autopilot이 이를 보고 선제적으로 판단한다.

# autopilot 측 코드 (이전 글에서 소개)
BUDGET=$(~/.claude/scripts/token-budget-advisor.sh --short)
if echo "$BUDGET" | grep -qE '🔴|critical|cap-near'; then
...

cost-summary.sh: 기간별 집계

~/.claude/scripts/cost-summary.sh # 7일분 (기본값)
~/.claude/scripts/cost-summary.sh 30 # 30일분
~/.claude/scripts/cost-summary.sh today # 오늘만

per day:는 유니코드 막대 그래프(Bar graph)를 사용하여 사용량을 시각화한다.

bar = "█" * min(40, int(d["cost"] * 4))
print(f" {day}: ${d['cost']:5.2f} ({d['n']}s) {bar}")

per model:

모델별 메시지 수도 출력된다. "Sonnet 사용량이 많은 날에는 무엇을 했는가"를 되돌아보는 데 사용하고 있다.

빠졌던 함정들

  • rate_limits가 세션 시작 직후에 존재하지 않아 // empty로 폴백(Fallback) 처리.
  • 첫 번째 턴(Turn)이 반환될 때까지 --로 표시하여 정상 작동 확인.
  • ccusage daily가 statusline을 차단함 → 120초 캐시 + &를 사용하여 비동기 처리. 데이터가 오래되었더라도 렌더링을 멈추지 않음.
  • 헬스 체크(Health check) 시마다 무거워짐 → 5분 캐시 + & 적용. 업데이트 중에는 ·만 반환.
  • Linux에서는 stat -f %m이 macOS 전용이므로 stat -c %Y로 변경. 스크립트는 macOS를 전제로 작성됨.
  • ccusage와 자체 집계 값의 괴리를 인지하지 못함source_diff_pct를 출력하여 항상 교차 검증(Cross-validation)할 수 있도록 함.
  • stdin JSON을 디버그 로그(/tmp/cc-statusline-last.json, rate_limits가 없다고 착각하여 엔드포인트를 호출하려 했던 로그)로 내보내는 코드를 처음부터 넣어두면 더 빨리 발견할 수 있음.
# 디버그용. 삭제해도 무방함
printf '%s' "$INPUT" > /tmp/cc-statusline-last.json 2>/dev/null || true

요약

  • Claude Code의 statusline 스크립트 stdin으로 .rate_limits.* / .context_window.used_percentage / .cost.total_cost_usd가 들어온다. 인증은 필요 없음.
  • 단 3줄로 "브랜치 / 5h·7d·ctx% / 세션+오늘의 비용(원화 환산) + 헬스"를 상시 표시.
  • 당일 비용은 2분 캐시 + 백그라운드 업데이트로 차단 없이 처리.
  • 헬스 체크 1글자는 5분 캐시로 경량화.
  • token-budget-advisor.sh가 ccusage 공식 값과 자체 집계 값을 교차 검증하며, autopilot 실행 여부를 판단하는 데 사용.

다음에는 이 표시를 포함한 자동화 전체를 한 번의 명령어로 점검하는 이야기 ―― 24/7 자동화의 생사 여부를 한 번의 명령어로 확인하기를 작성하겠습니다.

Discussion

AI 자동 생성 콘텐츠

본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0