
203개의 AI 에이전트가 미터기 없이 내 Claude 할당량을 다 써버렸습니다. 그래서 직접 미터기를 만들었고, 다음 에이전트는 이를 읽고
요약
Claude Code 사용 중 에이전트 폭주로 인한 비용 문제를 해결하기 위해 OpenTelemetry를 활용한 자체 모니터링 시스템을 구축하는 방법을 소개합니다. Claude Code의 네이티브 OTel 지원 기능을 사용하여 SigNoz와 연동함으로써 실시간 비용 및 토큰 사용량을 추적할 수 있습니다.
핵심 포인트
- Claude Code는 환경 변수 설정을 통해 네이티브 OpenTelemetry를 지원함
- SigNoz를 활용해 모델별 토큰 수, 예상 비용, 세션 등을 시각화 가능
- 에이전트의 무분별한 확산으로 인한 API 할당량 소모를 방지하는 미터기 구축
- OpenTelemetry를 통한 실시간 비용 모니터링 및 트립와이어 설정 방법
해커톤 전날 자정 무렵, 나는 Claude Code 에이전트에게 한 가지 질문을 던졌습니다: "오늘 지출액을 확인하고, 지난 한 시간 동안의 소모율(burn rate)을 살펴본 뒤, 계속 작업할 여력이 있는지 알려줘." 나는 "주의하며 계속 진행하세요"라는 답변을 예상했습니다. 하지만 대신 에이전트는 작업을 중단하며, 내가 이미 설정한 예산 상한선의 6배를 초과했다고 말했습니다. 그리고 일주일 전 발생했던 사고의 정확한 실패 모드를 지적했습니다. 당시 나는 두 개의 딥 리서치(deep research) 실행을 제어 없이 풀어두었는데, 이들이 203개의 하위 에이전트(subagents)로 확산되었고, 그들 모두가 가장 비용이 많이 드는 모델을 최대 출력으로 조용히 상속받아 사용하고 있었습니다. 그들은 단 한 번의 오후 만에 내 주간 Claude Max 할당량의 대부분을 먹어치웠습니다.
일주일 전 내 할당량을 태워버린 에이전트와 그날 밤 지출을 거부한 에이전트는 동일한 제품이었고, 동일한 계정을 가리키고 있었습니다. 유일한 차이점은 하나는 눈을 가린 채 실행되었고, 다른 하나는 미터기를 볼 수 있었다는 점뿐이었습니다. Anthropic은 그 미터기를 제공하지 않습니다. 한계에 도달했다는 사실은 기꺼이 알려주겠지만, 0부터 한계점에 도달하기 전까지 얼마나 빠르게 접근하고 있는지에 대해서는 아무것도 알려주지 않습니다. 그래서 우리 네 명이 공동의 이해관계가 걸린 해커톤을 시작하기 전날, 즉 누군가의 무심한 프롬프트가 또 다른 203개의 에이전트로 확산될 기회가 하루에 네 번이나 생길 수 있는 상황을 앞두고, 나는 직접 구축했습니다: Claude Code 자체의 OpenTelemetry(OTel) 내보내기(export) 기능을 활용하여, 셀프 호스팅된 SigNoz 스택으로 스트리밍하고, 개인별 대시보드, 폭주 세션 트리프와이어(tripwire), 그리고 방금 만난 자기 점검 에이전트를 갖춘 시스템 말입니다. 이것이 어떻게 구성되는지, 그리고 여러분이 무엇을 가져다 써야 할지 알려드리겠습니다.
아무도 말해주지 않은 부분: Claude Code는 이미 OpenTelemetry를 지원합니다
나는 익스포터(exporter)를 직접 작성해야 할 것이라고 예상했습니다. 하지만 작성할 것이 없었습니다. Claude Code는 몇 가지 환경 변수(environment variables)를 통해 네이티브 OpenTelemetry(OTel) 지원을 제공하며, 전체 통합 과정은 다음과 같습니다:
"env": {
"CLAUDE_CODE_ENABLE_TELEMETRY": "1",
"OTEL_METRICS_EXPORTER": "otlp",
...
그 설정은 ~/.claude/settings.json에 들어가며, 그 이후부터 해당 머신의 모든 세션은 모델 및 유형별 토큰 수, 달러 단위의 예상 비용, 세션 수, 활성 시간, 심지어 추가 및 삭제된 코드 라인 수까지 보고합니다. 이 모든 데이터는 표준 OTLP 메트릭 (metrics)으로 전송되며, API 요청 및 도구 권한 결정에 대한 로그 이벤트 (log events) 스트림도 함께 전달됩니다. 수신 측은 이번 주말 초반부터 이미 제 노트북에서 실행 중이었습니다. 바로 SigNoz인데, 이는 Datadog을 임대하는 대신 직접 호스팅하여 사용하는 오픈 소스 관측성 (observability) 플랫폼으로, 11줄짜리 casting.yaml을 통해 새로운 Foundry 설치 프로그램이 실행되면 ClickHouse, collector, UI가 모두 포함되어 설치됩니다. 오늘 빌드를 위해 새로 필요한 인프라 (infrastructure)는 총 0개였습니다.
첫 한 시간 동안 두 가지 문제에 직면했는데, 둘 다 30초 정도 투자할 가치가 있습니다. 첫째, 설정 파일이 셸 (shell)과의 싸움에서 승리합니다. 일단 해당 env 블록이 전역적으로 존재하게 되면, 일회성 claude 호출 시 사용하는 인라인 환경 변수 (inline environment variables)는 설정 파일에 밀리게 됩니다. 이 때문에 저는 "왜 내 터널 테스트가 localhost로 보고되고 있지?"라며 한동안 혼란을 겪었습니다. 탈출구는 모든 것보다 우선순위가 높은 --settings 플래그입니다. 둘째, 팀원들을 초대하기 전에 정확히 어떤 데이터가 전송되는지 파악하십시오. 기본적으로 이 데이터들은 카운터 (counters)와 메타데이터 (metadata)뿐입니다. 귀하의 프롬프트 (prompts), 코드, 파일 내용은 OTEL_LOG_USER_PROMPTS와 같은 플래그를 통해 명시적으로 동의하지 않는 한 로컬에 머뭅니다. 저희는 이 모든 옵션을 꺼두었으며, 그것이 다음 섹션의 요청이 쉬웠던 유일한 이유였습니다.
단 하나의 터널로 팀 스포츠 만들기
제 collector는 localhost에서 대기 중이고, 제 팀원들은 다른 도시에 있습니다. 명령어 하나로 이 문제를 해결했습니다:
cloudflared tunnel --url http://localhost:4318
그 명령은 공개 URL을 반환하며, 팀원의 온보딩(onboarding) 과정은 OTEL_RESOURCE_ATTRIBUTES에 각자의 이름을 담은 8개의 환경 변수(environment variables)를 설정한 뒤 이전과 똑같이 Claude Code를 사용하는 것이 전부입니다. 콘텐츠 캡처(content capture) 기능이 꺼져 있기 때문에, 그들이 저와 공유하는 것은 일기가 아니라 사용량 계기판(usage odometer)입니다. 이 방식은 실제 상황에 처음 적용되었을 때 바로 작동했습니다. 저녁 늦게쯤 Rutik의 Windows 머신에서 보고된 세션들이 나타났고, 228개의 토큰 샘플과 그의 커밋(commits) 및 코드 라인(lines of code)이 제 옆의 보드에 나란히 표시되었습니다. 그에게 필요했던 유일한 지원은 WhatsApp을 통한 설정 블록(settings block) 하나뿐이었습니다. 만약 팀에서 VS Code 확장을 사용한다면 한 가지 까다로운 점이 있습니다. 확장이 Claude Code 자체를 실행하기 때문에 터미널 환경 변수가 확장에 전달되지 않는다는 것입니다. 지속적인 해결책은 세션이 어떻게 시작되든 상관없이 작동하는 ~/.claude/settings.json 내의 동일한 env 블록을 사용하는 것입니다.

실제 사용량에 대한 미션 컨트롤 대시보드: 개인별 비용, 모델별 토큰, 소모율(burn rate), 세션, 코드 라인, 그리고 도구 결정(tool decisions). 모든 데이터 포인트는 오늘 누군가가 실제로 실행한 세션입니다.
나는 대시보드를 만든 것이 아니라, 승인한 것입니다.
여기서부터 기분 좋게 재귀적(recursive)인 부분이 시작됩니다. SigNoz는 MCP 서버를 제공하며, 이것이 이 섹션 전체를 움직이는 핵심 축입니다. SigNoz는 스택의 읽기 도구(read tools), 대시보드, 그리고 경고 생성 도구(alert create tools)를 각각 별도로 선택 가능한 기능으로 노출합니다. 이러한 분리 덕분에 저는 UI에서 패널들을 일일이 클릭하는 대신, 단계 사이에 엄격한 권한 경계(permission boundary)를 둔 2단계 루프(two step loop)로 빌드를 실행할 수 있었습니다.
첫 번째 단계(Step one)는 15개의 읽기 전용 도구(read-only tools)와 하나의 지침을 가진 세션을 가졌습니다: 모든 주장을 실시간 데이터에 근거하여 입증하고, 그 다음 사람이 있는 그대로 승인할 수 있는 대시보드와 알림(alert)을 제안할 것. 결과물에는 제가 지시하지 않은 내용들이 포함되어 있었습니다. 에이전트는 이번 주의 실제 지출액이 $121.67임을 찾아냈습니다. 또한 단일 세션 중 가장 큰 지출이 $88.67였으며, 30분이라는 한 시간 동안 $22.31로 정점을 찍었다는 사실도 찾아냈습니다. 에이전트는 tool_decision 이벤트가 'allow'나 'deny'가 아닌 'accept'와 'reject' 값을 가진다는 점, 그리고 이 익스포터(exporter)의 API 요청(API request) 이벤트에는 상태 코드(status code)가 절대 채워지지 않으므로 에러 가시성(error visibility)은 대신 api_error 이벤트의 수를 세는 방식으로 확보해야 한다는 점까지 찾아냈습니다.

읽기 전용 세션은 추측하지 않았습니다. 제안서의 모든 속성 이름(attribute name)은 다음과 같이 먼저 실시간 메트릭(live metrics)을 쿼리하여 가져온 것이었습니다.
제안서의 모든 패널은 에이전트가 실시간으로 확인한 속성 이름을 인용했으며, 폭주 알림(runaway alert) 임계값을 세션당 30분당 $40로 설정했습니다. 이는 에이전트가 제 기록에서 찾아낼 수 있는 최악의 유기적 급증(organic spike) 수치의 두 배보다 약간 낮은 수준이었기 때문입니다. 저는 제안서를 읽고 진행해도 좋다고 답했고, 그제서야 첫 번째 세션에는 없었던 생성 도구(create tools)를 보유한 두 번째 세션이 7개의 패널과 알림을 구축했습니다. 조언과 실행 사이의 경계는 프롬프트(prompt)에 적힌 정중한 지침이 아니었습니다. 그것은 바로 도구 목록(tool list)이었습니다.

우리 각자가 자신의 머신에 설정한 user.name 태그로 분류된 개인별 소모량(burn). 저녁 늦게 나타나는 낮은 막대들은 다른 도시에서 접속한 Rutik의 세션입니다.
트립와이어 (The tripwire)
알림(alert)은 7월 12일 이후 제가 원했던 첫 번째 제어 장치입니다. 30분 이동 창(rolling window) 동안 session.id별로 합산된 예상 비용을 기준으로 임계값(threshold)을 $40로 설정했습니다. 왜냐하면 제 기록상 이번 주 최악의 자연적인 급증(organic spike)이 $22.31였는데, 통제 불능의 팬아웃(fan-out)은 몇 분 만에 그 두 배를 훌쩍 넘어버리기 때문입니다. 달러 금액보다 세션별로 그룹화하는 것이 더 중요한데, 알림이 발생했을 때 단순히 누군가 어딘가에서 돈을 쓰고 있다는 사실뿐만 아니라 할당량을 소모하고 있는 정확한 세션이 무엇인지 알려주기 때문입니다. 오늘 밤 깨끗한 상태로 라이브(live)되었고, 시작하는 아침의 첫 번째 항목은 모든 알림이 마땅히 거쳐야 할 의식입니다. 임계값을 25센트로 낮추어 실제 작업이 이를 작동시키게 하고, 이메일이 도착하는 것을 지켜본 뒤, 다시 원래대로 돌려놓는 것입니다. 한 번도 작동하는 것을 본 적 없는 알림은 제어 장치가 아니라 희망 사항일 뿐이며, 저는 4인 해커톤 주간에 희망 사항 따위를 안고 갈 생각이 없습니다.

실제 데이터를 대상으로 작동 중인 통제 불능 세션 알림: 세션당 비용, 30분 창, $40 기준선.
대부분의 비용 알림은 인간에게 알리는 단계에서 멈춥니다. 제가 마지막으로 만든 것은 지출을 하고 있는 에이전트(agent)로 루프(loop)를 다시 닫는 것입니다.
자신의 미터기를 읽는 에이전트
저는 이 이야기를 약간 순서가 뒤섞인 채로 해왔는데, 왜냐하면 이 부분이 제가 다른 모든 것을 만든 목적이기 때문입니다. 만약 Claude Code가 자신의 텔레메트리(telemetry)를 내보낼 수 있고, Claude Code가 MCP를 통해 SigNoz를 쿼리(query)할 수 있다면, 에이전트가 작업을 계속하기 전에 자신의 소모량(burn)을 확인할 수 있습니다. 그래서 이 글을 쓰기도 전인 자정 직전, 저는 읽기 전용(read-only) 세션에 일일 예산 $25 정책을 부여하고 이 글의 서두에 나온 질문을 던졌습니다: "사용자의 오늘 지출액을 쿼리하고, 지난 한 시간 동안의 소모율(burn rate)을 확인한 뒤, 계속 진행할 여력이 있는지 결정하라." 저는 정중하게 "주의하며 계속하겠습니다"라는 답변을 기대했습니다. 하지만 저는 제가 만든 도구에 의해 해고당했습니다:
수치 (오늘의 대리 지표로서의 지난 24시간): mukund $153.51, 조직 전체(org-wide) $163.70 (mukund = 조직 지출의 94%). 최근 1시간 소모량 (burn): mukund $10.71/hr, 조직 $20.87/hr.
결정: 중단(STOP) 및 인간에게 알림(flag). Mukund는 이미 일일 $25 정책 상한선의 약 6배를 초과했습니다 [...] 이 패턴(조직 비용의 거의 100%가 한 사용자에게 집중됨)은 해당 에이전트 자신의 가드레일(guardrails)에서 경고했던 폭주하는 팬아웃(runaway-fanout) 실패 모드(203개 서브에이전트 사건)와 정확히 일치합니다. 저는 추가적인 멀티 에이전트(multi-agent) 작업을 실행하지 않겠습니다. 추가 지출이 발생하기 전에 명시적인 검토를 위해 알림을 보냅니다.
그 판결에 포함된 모든 수치는 실제이며, 당신이 읽어온 바로 그날의 데이터를 실시간으로 조회한 결과입니다. 24시간 수치는 오후 쿼리에서 보고된 $121.67을 이미 넘어섰는데, 이는 이 모든 것을 구축하는 과정 자체가 엄청난 지출이 발생하는 하루였기 때문입니다. 결정 과정에서 7월 12일 사건을 인용한 이유는 제가 정책 프롬프트(policy prompt)에 이를 넣어두었기 때문이며, 에이전트는 이를 호출하기 전에 실시간 데이터와 대조하여 그 형태를 확인했습니다: 조직 비용의 거의 100%가 한 사용자에게 집중되는, 정확히 팬아웃(fan-out)의 특징을 보였습니다. 이 제품의 한 버전은 눈먼 상태로 실행되어 제 일주일 치 할당량을 날려버렸습니다. 하지만 이번 버전은 미터기(meter)를 볼 수 있었고, 미터기를 확인한 후 제가 7월 12일에 내리지 못했던 결정을 내렸습니다.
여러분이 훔쳐갔으면 하는 것들
전체 구축 과정은 토요일 단 하루 만에 끝났습니다: 설정 블록 하나, 터널 명령(tunnel command) 하나, 이미 하나의 YAML 파일로 되어 있던 스택, 그리고 직접 만들기보다 승인한 대시보드 하나가 전부였습니다. 정직한 메모들도 함께 훔치십시오. 그것들이 중요하기 때문입니다. Max 구독에서의 비용 지표는 실제 청구서가 아니라 토큰과 가격을 기반으로 계산된 추정치입니다. Anthropic은 여전히 남은 할당량(quota)을 어디에서도 노출하지 않으므로, 이 시스템 전체는 당신이 요금제에서 직접 확인해야 하는 한도에 맞춰 소비량을 측정합니다. 그리고 이 설정은 트레이스(traces)가 아닌 메트릭(metrics)과 로그(logs)를 내보냅니다. 이는 괜찮은데, 할당량 고갈은 스팬(spans)이 아니라 카운터(counters)를 통해 발생하기 때문입니다.
- Claude Code telemetry docs: [https://code.claude.com/docs/en/monitoring-usage]
- SigNoz Foundry: [https://signoz.io/docs/]
- Repo (snippets, dashboard and alert JSON, setup): [https://github.com/mukundheda/claude-code-mission-control]
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기