본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 22. 19:30

Claude Code 상태 표시줄의 'cache 4m23s' 문구는 실제로 정확할까?

요약

Claude Code 상태 표시줄의 프롬프트 캐시 카운트다운 로직이 어떻게 작동하는지 분석합니다. Anthropic의 프롬프트 캐시 만료 시간을 계산하는 방식과 설정 오류 시 발생할 수 있는 오차 원인을 설명합니다.

핵심 포인트

  • 프롬프트 캐시 카운트다운은 가장 최근 assistant 응답의 타임스탬프를 기준으로 계산됨
  • 기본 설정에서는 정확하지만, 캐시 TTL 변경 시 오차가 발생할 수 있음
  • 데이터 캐시와 프롬프트 캐시의 개념적 차이를 구분해야 함

💡 원래 제 블로그 blog.leeguoo.com 에 게시되었습니다 — 리버스 엔지니어링 (reverse engineering), AI 에이전트 (AI agents), 그리고 실제 배포 가능한 제품을 만드는 것에 대한 현장 기록입니다.

제가 Claude Code를 위해 작성한 상태 표시줄 cs / claude-statusbar에는 cache 4m23s라고 표시되는 줄이 있습니다. 이 줄은 초록색으로 매초 카운트다운이 되다가, 끝에 도달하면 빨간색 cache COLD로 변합니다.

누군가 제게 물었습니다: 이 숫자는 정확히 어떻게 계산되며, 정말 정확한가요?

바쁜 분들을 위한 한 줄 요약: 기본 설정과 5분 캐시(cache)를 사용하는 경우 정확합니다. 이 숫자가 체계적으로 거짓말을 하는 유일한 시나리오는 1시간 캐시를 활성화했지만 TTL을 변경하지 않았을 때입니다. 이 경우, 55분이나 일찍 보고하게 됩니다. 설정 한 줄이면 해결됩니다. 그 이유는 아래에 설명되어 있습니다.

먼저, 두 가지 "캐시 (cache)"를 구분하세요. 혼동하지 마세요

이 저장소(repo)에는 캐시라고 불리는 두 가지가 있습니다. 따라서 "정확한가요?"라고 묻기 전에 우리가 무엇을 의미하는지 명확히 해야 합니다:

  • 데이터 캐시 (Data cache): cache.pyCACHE_MAX_AGE_S = 30. 이는 상태 표시줄이 초당 한 번씩 다시 그려질 때마다 매번 서브프로세스 (subprocess)를 실행하지 않도록 claude-monitor 출력을 30초 동안 캐싱합니다. 이것은 카운트다운의 정확도와는 아무런 관련이 없습니다.
  • 프롬프트 캐시 카운트다운 (Prompt-cache countdown): 오늘의 주인공입니다. 이것은 "Anthropic의 프롬프트 캐시 (prompt cache)가 만료될 때까지 시간이 얼마나 남았는가"를 계산합니다.

이후의 내용은 두 번째 것에 대해서만 다룹니다.

기준점 (Where It Anchors)

로직은 매우 짧습니다: 단 하나의 함수, get_cache_age_text가 수행합니다. 이 함수는 세 가지 일을 합니다:

  1. ~/.cache/claude-statusbar/last_stdin.json을 읽어 현재 세션의 transcript_path를 가져옵니다.
  2. 해당 JSONL 파일을 역순으로 읽어 type == "assistant"가장 최근의 레코드를 찾고, 그 timestamp를 가져옵니다.
  3. remaining = ttl_seconds - 경과 초(elapsed seconds)를 계산한 다음, 이를 카운트다운 형식으로 포맷팅합니다.

두 번째 단계는 _last_assistant_age에서 수행되며, 핵심 부분은 다음과 같습니다:

if entry.get("type") != "assistant":
    continue
...
...

기준점(anchor point)에 주목하세요: 파일의 수정 시간(mtime)도, 사용자 메시지도 아닌, 가장 최근 어시스턴트(assistant) 메시지의 타임스탬프입니다. 이 선택은 올바릅니다. 다음 섹션에서 그 이유를 설명합니다.

공식도 매우 간단합니다:

remaining = ttl_seconds - age_s
if remaining <= 0:
    return "COLD"

ttl_seconds의 기본값은 300입니다. 만약 remaining <= 0이거나, 어시스턴트 레코드를 전혀 찾을 수 없는 경우(age_s is None), "COLD"를 반환합니다. 만약 transcript_path조차 없다면 빈 문자열을 반환하고 세그먼트 전체를 숨깁니다.

여기에 있는 김에 약간의 역사를 덧붙이자면: v3.2.2 PR 이전에는 이 라인이 대신 "이미 경과한 시간"을 표시했습니다. 이후 카운트다운 방식으로 변경되었는데, 사용자가 실제로 알고 싶은 것은 "마지막 응답 이후 몇 분이 지났는가"가 아니라 "캐시가 만료되기 전에 메시지를 하나 더 보낼 시간이 남아 있는가?"이기 때문입니다. 카운트다운은 이에 대해 직접적인 답을 제공하지만, 경과 시간은 여전히 머릿속으로 뺄셈을 하게 만듭니다.

Anthropic의 실제 동작을 정확하게 모델링하고 있는가?

공식 문서를 확인해 보면, 프롬프트 캐싱 (Prompt caching)에서 두 문장이 분위기를 설정합니다:

기본적으로 캐시는 5분의 수명을 가집니다.

캐시된 콘텐츠가 사용될 때마다 추가 비용 없이 캐시가 갱신됩니다.

다시 말해, TTL (Time To Live)은 슬라이딩 윈도우 (sliding window) 방식입니다. 즉, 캐시 히트 (cache hit)가 발생할 때마다 TTL이 5분으로 재설정됩니다.

이는 왜 “가장 최근의 어시스턴트 턴 (assistant turn)에 앵커링 (anchoring)하는 것”이 올바른 방식인지 설명해 줍니다. 각 추가 응답마다 age_s가 0으로 재설정되고, 카운트다운이 자동으로 다시 채워지며, 이는 “한 번 사용하면 한 번 갱신한다”는 서버 측 동작과 일치합니다. 코드 내의 주석인 # 5min — Anthropic's default prompt cache TTL은 틀리지 않았습니다. 이 계층(layer)에서 모델은 정확합니다.

부정확한 부분 — 증거와 함께

이것이 핵심입니다. 가장 치명적인 것부터 덜 중요한 순서대로 세 가지 계층을 나열하겠습니다.

1. 기본 TTL은 5분으로 하드코딩되어 있지만, 실제로는 1시간 캐시를 실행 중일 수 있음

이 부분은 사용자들을 진정으로 오도할 수 있는 유일한 지점입니다. 증거는 제 컴퓨터의 가장 최근 어시스턴트 기록에 있는 usage 블록에서 확인할 수 있습니다:

"cache_creation": {
  "ephemeral_1h_input_tokens": 1421,
  "ephemeral_5m_input_tokens": 0
...

모든 것이 1시간 버킷 (bucket)으로 들어갔습니다. 다시 말해, 이 기기는 실제로 60분의 실제 수명을 가진 1시간 캐시를 실행 중입니다. 하지만 cs는 기본적으로 cache_ttl_seconds = 300으로 설정되어 있으므로, 5분이 지나면 실제 진실보다 55분이나 일찍 cache COLD라고 외칠 것입니다.

가장 아이러니한 부분은, 5분과 1시간 중 무엇을 결정할지에 대한 “진실 신호”(ephemeral_1h_input_tokensephemeral_5m_input_tokens)가 이미 열려 있는 동일한 파일의 동일한 기록 안에 바로 놓여 있다는 점입니다. 하지만 _last_assistant_agetypetimestamp 필드만 읽고 해당 usage 블록은 그대로 지나쳐 버립니다. 이론적으로는 트랜스크립트 (transcript)로부터 어떤 TTL을 사용할지 자동으로 추론할 수 있습니다. 현재로서는 cs config set cache_ttl_seconds 3600을 수동으로 실행해야 합니다. 이는 수정할 가치가 있는 TODO 항목입니다.

2. 앵커는 “캐시가 갱신된 시점”이 아니라 “턴이 종료된 시점”임

어시스턴트(assistant) timestamp는 해당 턴의 작성이 **종료(finished)**된 시점과 대략 일치합니다. 반면, 캐시는 요청이 **전송(sent)**될 때 서버 측에서 갱신됩니다. 이 두 지점 사이에는 생성 지연 시간(generation-latency) 차이가 존재합니다. 실제 트랜스크립트의 동일한 구간에서 추출한 어시스턴트 타임스탬프는 다음과 같습니다:

assistant  2026-05-29T04:46:18.432Z
assistant  2026-05-29T04:46:19.653Z
assistant  2026-05-29T04:46:25.680Z

이는 몇 초에서 십여 초 정도의 차이입니다. 300초 또는 3600초의 TTL(Time To Live)과 비교하면 무시할 수 있는 수준입니다. 방향성 측면에서 보면 아마도 낙관적일 것입니다. 즉, 표시된 남은 시간이 실제 서버 측 값보다 약간 더 높게 나타날 수 있습니다. 하지만 문제가 될 정도로 큰 차이는 아닙니다.

여기서 솔직해져야겠습니다. 소스 코드만으로는 Anthropic의 서버가 요청 시작 시점부터 카운트를 시작하는지, 아니면 요청 종료 시점부터 시작하는지 증명할 수 없습니다. 따라서 정확한 진술은 다음과 같습니다: 앵커(anchor)는 캐시가 갱신되는 정확한 순간이 아니라, 한 턴의 지연 시간 범위 내에서 정확한 대리 지표(proxy)입니다. 충분히 쓸만하지만, 스톱워치처럼 다루지는 마세요.

3. 색상은 숫자가 아니라 문자열을 통해 추측함

흥미로운 엔지니어링 트레이드오프(tradeoff)입니다. _cache_severity는 남은 초(seconds)를 전달받는 것이 아니라, 이미 포맷팅된 문자열을 전달받은 뒤 m 또는 h가 포함되어 있는지 확인합니다:

if cache_text == "COLD":
    return theme.s_hot          # red
if "m" in cache_text or "h" in cache_text:
...

남은 시간이 1분 미만일 때, 포맷터(formatter)는 색상 결정기(colorizer)가 "노란색으로 변할 시점"을 감지할 수 있도록 의도적으로 m 없이 순수하게 Ys만 출력합니다. 포맷터와 색상 결정기 사이에는 암묵적인 계약(contract)이 존재합니다. 리포지토리에는 이 계약을 고정하기 위한 전용 test_cache_severity.py 파일까지 있어, 향후 포맷 변경으로 인해 색상이 조용히 뒤섞이는 것을 방지합니다. 작동은 잘 되지만, 이는 결합(coupling)된 구조이므로 알아둘 가치가 있습니다.

또 다른 예외 상황(edge case)이 있습니다: 트랜스크립트(transcript)를 역순으로 읽을 때 320KB 제한(10×32KB)이 적용됩니다. 만약 거대한 트랜스크립트 내에서 마지막으로 스캔된 320KB 안에 어시스턴트(assistant) 기록이 포함되어 있지 않다면, 이는 COLD 상태로 처리됩니다. 이는 성능을 위한 트레이드오프(tradeoff)입니다. 상태 표시줄은 매초 다시 그려지기 때문에, 매번 수 MB를 스캔할 수는 없기 때문입니다. 일상적인 사용 환경에서는 이 제한에 걸릴 일이 없을 것입니다.

그래서, 정확할까?

  • 5분 캐시 + 기본 설정: 정확합니다. 앵커(anchor)가 올바르고, 슬라이딩 윈도우(sliding-window) 모델이 정확하며, 예외 상황들도 처리되어 있습니다. 즉, 시계 역전(clock rollback)은 0으로 제한되고, 단순한 타임스탬프(timestamp)는 UTC로 처리되며, Z 접미사는 정규화(normalized)됩니다.

  • 1시간 캐시 + 변경되지 않은 TTL: 시스템적으로 55분 일찍 보고될 것입니다. 한 줄의 명령어로 이를 수정할 수 있습니다: cs config set cache_ttl_seconds 3600.

  • 초 단위 정밀도: 기대하지 마세요. 앵커 자체에 한 번의 왕복 지연 시간(round-trip latency)으로 인한 근사 오차(proxy error)가 있습니다. 이것은

  • 🔧 도구: GitHub의 claude-statusbar — Claude Code 상태 표시줄 — 5h/7d 속도 제한 (rate-limit) 사용량 + 재설정 카운트다운.

  • 📝 더 많은 글: blog.leeguoo.com — 저는 작은 AI 에이전트 도구와 CLI를 만드는 풀스택 개발자 Guo Li (leeguoo)입니다.

  • 💬 도움이 되었나요? 리포지토리에 ⭐를 남겨주시거나 여기서 팔로우해주시면 큰 힘이 됩니다.

AI 자동 생성 콘텐츠

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

원문 바로가기
0

댓글

0