클로드 토큰 가만히 둬도 줄어드는 이유 5가지 — 알림 1건이 4.7턴을 부른다
3줄 요약
- 클로드 코드는 매 요청마다 대화 전체를 다시 보낸다. 그래서 사람이 아무것도 안 쳐도 새 턴이 하나 생기면 그 턴은 전체 컨텍스트만큼 토큰을 쓴다.
- 가만히 둔 세션에서 새 턴을 만드는 것은 5가지다: 캐시 만료 뒤 첫 메시지, 예약 작업, 다른 세션의 메시지, 목표(goal) 점검, 백그라운드 작업 알림.
- 이 프로젝트 실측에서 캐시 만료 재기록은 낭비 1위(11.8%)였고, 세션 간 알림 1건은 평균 4.7턴을 불러 한 세션 비용의 40.9%를 차지했다.
| 질문 | 답 |
|---|---|
| 왜 가만히 둬도 토큰이 줄어드나 | 사람 대신 무언가가 새 턴을 만들고, 턴마다 대화 전체가 다시 실리기 때문 |
| 무엇이 새 턴을 만드나 | 캐시 만료 후 첫 메시지·예약 작업·세션 간 메시지·goal 점검·백그라운드 알림 |
| 가장 큰 것은 무엇이었나 | 캐시 만료 재기록. 이 프로젝트 대화 기록 107개 스캔에서 낭비의 11.8% |
| 알림은 얼마나 드나 | 2026-08-23 실측 1건당 평균 4.7턴, 세션 비용의 40.9% |
| 공짜로 줄이는 법은 | 주제가 바뀌면 /clear. 공식 문서 표현으로 "/clear costs nothing" |
이 글대로 하면 무엇이 달라지는가: "아무것도 안 했는데 왜 줄었지"를 5개 항목 중 어디에 해당하는지로 판별할 수 있게 되고, 자기 세션 기록에서 그 비용을 직접 재는 스크립트를 손에 쥐게 된다.
클로드 토큰은 왜 가만히 둬도 줄어드나?
새 턴 하나가 대화 전체를 다시 싣기 때문이다. 클로드 코드 비용 문서(code.claude.com/docs/en/costs)는 "Claude Code sends your full conversation with every request"라고 적는다. 프롬프트 캐시가 있으면 그 이력을 싼 캐시 단가로 다시 읽지만, 다시 읽는다는 사실은 변하지 않는다. 그래서 하루 종일 열어 둔 세션에서 한 줄짜리 질문을 해도 대화 전체만큼 사용량이 잡힌다.
여기서 나오는 비용 공식이 이 글 전체의 축이다.
세션 비용 ≈ 평균 컨텍스트 크기 × 턴 수
이 프로젝트의 대표 세션 1개(2026-08, 요청 471건)를 분해했을 때 캐시 읽기는 216.5M 토큰, 출력은 0.43M 토큰이었다. 평균 컨텍스트 약 458k × 471턴 ≈ 216M으로 공식과 맞는다. 출력이 아니라 다시 읽기가 비용의 본체라는 뜻이다. 사람이 친 턴이든 알림이 만든 턴이든 이 곱셈에서는 똑같이 1턴이다.
가만히 둔 세션에서 새 턴을 만드는 5가지는 무엇인가?
공식 문서의 "Why usage climbs in a long session" 항목과 이 프로젝트 운영 기록을 합치면 5가지로 정리된다.
| # | 원인 | 무엇이 일어나나 | 끄거나 줄이는 법 |
|---|---|---|---|
| 1 | 캐시 만료 후 첫 메시지 | 캐시 수명이 지난 뒤 첫 요청이 전체 컨텍스트를 새로 처리한다 | 1시간 이상 비웠다 오면 /clear 후 재개 |
| 2 | 예약 작업(/loop 등) |
세션이 놀고 있어도 주기마다 전체 컨텍스트를 보낸다 | /usage의 Loops 행에서 확인하고 끄거나 주기를 늘린다 |
| 3 | 다른 세션의 메시지 | 세션이 놀고 있을 때 도착한 메시지가 새 턴으로 배달된다 | crossSessionInbound를 hold로 |
| 4 | goal 점검 | 백그라운드 작업을 기다리는 goal이 있으면 클로드가 상태를 확인하는 턴을 연다 | CLAUDE_CODE_GOAL_CHECKIN_MINUTES=0 |
| 5 | 백그라운드 작업·감시 알림 | 백그라운드 명령 완료, 감시 스크립트의 출력 한 줄이 각각 새 턴을 깨운다 | 알림을 묶어서 완료·중단 때만 깨운다 |
1~4번은 공식 문서에 그대로 있다. goal 점검은 문서상 목표 하나당 사용자 프롬프트 사이에 최대 3회로 제한된다(v2.1.246 이전에는 제한이 없었다). 5번은 공식 문서의 3번과 같은 원리를 이 프로젝트가 자기 도구에서 잰 것이다.
비교 삼아 적으면, 공식 문서가 말하는 "진짜 백그라운드" 비용(이전 대화 요약, /usage 같은 상태 조회)은 보통 세션당 $0.04 미만이다. 가만히 둔 세션의 토큰을 먹는 것은 이것이 아니라 위 표의 새 턴들이다.
캐시 만료 재기록은 실제로 얼마나 컸나?
이 프로젝트에서 측정한 낭비 항목 중 1위였다. 2026-08-10 waste_scan.py로 08-04 이후 대화 기록(전사) 107개를 훑은 결과는 다음과 같다.
| 항목 | 건수 | 전체 대비 |
|---|---|---|
| 캐시 만료 뒤 재기록 | 87건 | 11.8% |
| 진짜 압축(compaction) | 42건 | 캐시 만료 재기록의 약 1/6.7 |
| 단건 최대 재기록 | 1건 | 778,949토큰(10.6시간 공백 뒤 첫 요청) |
이 측정에서 비율 계산에 쓴 사용량 단위(%p)는 이 프로젝트의 추정 환산값이라, 위 표에는 비율과 토큰 수만 남겼다. 비율끼리의 비교는 같은 환산을 양쪽에 쓰므로 유지된다.
이 낭비가 잘 안 보이는 이유도 같이 잡혔다. 압축은 화면에 압축이라고 표시되지만, 캐시 만료 재기록은 그냥 평범한 다음 메시지처럼 지나간다. 공식 문서 기준 캐시 수명은 구독 1시간, 사용 크레딧(usage credits)을 쓰는 중이면 5분, API 키는 기본 5분이다. 점심을 먹고 돌아와 "계속해"라고 친 그 한 줄이, 세션이 컸다면 수십만 토큰짜리 재기록이 된다.
최근 버전에는 이것을 직접 보는 줄이 생겼다. v2.1.251 이상에서 /usage의 Session 블록에 Prompt cache (main) 줄이 나오고, 캐시 미스 횟수와 마지막 미스에서 다시 쓴 토큰 수, 지금 캐시가 warm인지 cold인지를 보여 준다.
알림 1건이 왜 4.7턴을 부르나?
알림이 잘린 채 도착해서, 잘린 나머지를 읽으러 턴을 더 쓰기 때문이다. 이 프로젝트는 여러 클로드 코드 세션이 서로 편지를 주고받는 사내 메시지 버스를 쓰고, 도착 알림을 감시 스크립트가 세션에 밀어 넣는다. 2026-08-23 한 세션(요청 172건, requestId 중복 제거 후)을 분해한 결과는 이렇다.
- 실측: 알림 15건이 요청 70건을 불렀다. 실측 평균은 1건당 4.7턴이다.
- 측정 결과 그 70건이 세션 전체 비용의 40.9%였다.
- 같은 세션에서 측정한 캐시 만료 재기록은 4회, 619,876토큰이었다.
오진 사슬 — 알림 비용을 "건수"로 읽으면 생기는 일
- 이 프로젝트 기록처럼 알림이 하루 15건이면 "15턴 정도 쓰겠지"라고 어림한다.
- 실제로는 알림 한 건 뒤에 턴이 줄줄이 붙는다. 흐름으로 보면 알림을 받는 턴, 잘린 본문을 읽는 턴, 판단·회신하는 턴, 회신 도구의 결과를 받는 턴이 이어지는 구조다(이 4단 분해는 흐름 설명이고, 실측한 것은 평균 4.7턴이라는 합계다).
- 원인을 따라가 보니 감시 스크립트(
cc_bus_watch.py)가 본문을 앞 200자로 잘라 보내고 있었다(_요약()의[:200]). 잘리지 않았다면 2번 턴이 없다. - 그래서 비용 단위는 "알림 건수"가 아니라 "알림당 턴 수"다. 같은 측정에서 캐시 만료와 비교한 손익분기는 알림 2건이었다 — 알림 2건이 만드는 턴이면 캐시 재기록 한 번과 맞먹는다.
이 결론은 운영 규칙으로 바뀌었다. 감시는 대화가 아니라 배치 코드가 하고, 대화는 완료·중단 때 1회만 깨운다. 이 프로젝트 규칙의 예시대로, 글 50편을 돌리면서 편마다 알리면 그것만으로 50턴이 생긴다.
내 세션의 유휴 비용은 어떻게 재나?
세션 기록 파일(~/.claude/projects/<프로젝트>/*.jsonl)에서 직접 잴 수 있다. 재는 순서는 네 단계다.
- 세션 파일을 한 줄씩 JSON으로 읽는다. 한 줄이 아주 길 수 있으니 grep으로 잘라 읽지 않는다.
type이assistant인 줄만 모으고requestId로 중복을 지운다. 한 요청이 여러 줄로 기록되기 때문이다.- 요청마다 직전 요청과의 시간 간격과
cache_creation_input_tokens를 본다. - 간격이 캐시 수명(구독 기준 1시간)을 넘은 뒤의 첫 요청을 "만료 재기록"으로 집계한다.
import json, sys
from datetime import datetime
def load_requests(path):
seen, reqs = set(), []
with open(path, encoding="utf-8") as f:
for line in f:
try:
d = json.loads(line)
except ValueError:
continue
rid = d.get("requestId")
if d.get("type") != "assistant" or not rid or rid in seen:
continue
seen.add(rid)
u = (d.get("message") or {}).get("usage") or {}
ts = datetime.fromisoformat(d["timestamp"].replace("Z", "+00:00"))
reqs.append((ts, u.get("cache_creation_input_tokens", 0), u.get("cache_read_input_tokens", 0)))
return sorted(reqs)
reqs = load_requests(sys.argv[1])
ttl = 3600 # 구독 기준 캐시 수명(초). API 키·사용 크레딧이면 300
expired = [(b[0], b[1]) for a, b in zip(reqs, reqs[1:]) if (b[0] - a[0]).total_seconds() > ttl]
print(f"고유 요청 {len(reqs)}건 · 만료 재기록 {len(expired)}건 · 재기록 토큰 {sum(t for _, t in expired):,}")
2단계의 중복 제거를 빼면 숫자가 부풀려진다. 2026-10-10 이 프로젝트의 세션 파일 1개를 실측하니 assistant 줄은 463개인데 고유 requestId는 218개였다. 이 실측대로라면 중복을 안 지울 때 2배 넘게 과대 집계된다.
python idle_cost.py ~/.claude/projects/<프로젝트-폴더>/<세션-id>.jsonl
알림이 부른 턴을 세고 싶다면 같은 파일에서 사용자 입력이 아닌 줄(알림·작업 완료 통지)이 들어온 직후의 요청 수를 세면 된다. 그 직후 다음 사람 입력까지 이어진 요청이 그 알림의 턴이다.
토큰 낭비를 줄이려면 무엇부터 하나?
효과가 큰 순서로 아래 체크리스트를 따른다. 앞의 둘은 비용이 0이다.
- [ ] 주제가 바뀌면
/clear. 공식 문서 원문이 "/clearcosts nothing"이다 - [ ] 캐시 수명 기준(구독 1시간) 이상 비웠다 돌아오면 이어 치지 말고
/clear후 재개한다(Pro·Max는 큰 세션을 요약에서 재개하는 제안도 뜬다) - [ ]
/usage의 Loops 행에서 놀 때 도는 예약 작업을 확인한다 - [ ] 다른 세션 메시지를 즉시 받을 필요가 없으면
crossSessionInbound: hold - [ ] 백그라운드 감시는 성공 한 줄마다 깨우지 않고, 완료·중단 때만 1회 깨우게 짠다
- [ ] 알림이 본문을 잘라 보내지 않는지 확인한다(잘리면 읽으러 턴이 하나 더 생긴다)
한계 — 이 방법이 안 통하는 경우
/clear는 맥락을 버린다. 긴 디버깅 중간이라면 버리는 비용이 더 크다. 이때는 /compact가 대안이지만, 공식 문서는 압축 자체도 대화 전체를 읽는 큰 요청이라고 적는다. 또 위 스크립트는 로컬 기록만 보므로 다른 기기나 claude.ai 앱의 사용량은 잡지 못한다. 한도는 앱과 공유되므로 앱 쪽 사용량은 이 측정의 사각지대다.
캐시 만료와 압축의 관계는 압축은 누적이 아니라 한 턴에서 온다는 실측에서, 사고(thinking)가 컨텍스트에서 차지하는 몫은 컨텍스트 중 사고 비중에서 따로 쟀다. 서브에이전트 쪽 비용은 서브에이전트의 실제 토큰 비용에 있다.
자주 묻는 질문
Q. 클로드 코드를 켜 두기만 해도 토큰이 드나?
A. 세션을 열어 두는 것만으로는 거의 들지 않는다. 공식 문서 기준 백그라운드 요약·상태 조회는 보통 세션당 $0.04 미만이다. 토큰을 쓰는 것은 예약 작업·다른 세션 메시지·goal 점검·백그라운드 알림이 만든 새 턴이다.
Q. 클로드 토큰 사용량이 점심 먹고 오면 크게 뛰는 이유는?
A. 캐시 수명이 지나서다. 공식 문서 기준 구독은 1시간, 사용 크레딧 사용 중이면 5분이다. 수명이 지난 뒤의 첫 메시지가 전체 컨텍스트를 새로 처리한다. 이 프로젝트 스캔에서 이것이 낭비의 11.8%로 1위였다.
Q. 다른 세션에서 온 메시지가 토큰을 쓰나?
A. 쓴다. 놀고 있는 세션에 배달되면 새 턴이 되고 전체 컨텍스트를 보낸다. 즉시 받을 필요가 없으면 crossSessionInbound를 hold로 둔다.
Q. 알림 1건이 왜 1턴이 아니라 여러 턴이 되나?
A. 알림을 받는 턴, 잘린 본문을 읽는 턴, 판단·회신하는 턴, 회신 결과를 받는 턴이 이어지기 때문이다. 이 프로젝트 실측에서 알림 15건이 요청 70건, 1건당 평균 4.7턴이었다.
Q. 내 세션의 낭비를 직접 잴 수 있나?
A. 있다. ~/.claude/projects/ 아래 세션 jsonl을 줄 단위로 읽어 requestId로 중복을 지우고, 요청 간격이 캐시 수명을 넘은 뒤의 cache_creation_input_tokens를 합치면 된다. 본문의 스크립트가 그 최소 형태다.
새 글을 메일로 받아보기
광고 없이 새 글과 자료만 보냅니다. 언제든 해지할 수 있습니다.
설치 없이 바로 쓰는 무료 도구
입력값은 서버로 전송되지 않고 브라우저에서만 계산합니다.
Comments
Comments (0)
Leave a Comment