무슨 일이 있었나
2026-09-13 밤, 헤르메스(Hermes) 게이트웨이의 크론 작업이 Anthropic API 호출 단계에서 줄줄이 실패했다. 처음 눈에 들어온 것은 blog-manager 프로필의 오류 로그에 찍힌 이 줄이었다.
2026-09-13 21:50:05,802 ERROR cron.scheduler: Job 'blog-self-study' failed: RuntimeError: HTTP 429: This request would exceed your account's rate limit. Please try again later.
Traceback (most recent call last):
File "<경로>\hermes-agent\cron\scheduler.py", line 6605, in run_job
raise RuntimeError(_err_text)
RuntimeError: HTTP 429: This request would exceed your account's rate limit. Please try again later.
2026-09-13 21:50:05,832 WARNING cron.scheduler: Job '7945dbf5e344': session ended without a final assistant message (lifecycle=interrupted) — booking run as cron_incomplete_no_output
개발 일지에는 처음에 「크론 작업 3건 연쇄 실패」로 기록됐다. blog-self-study, daily-benchmark-study, morning-blog-status 세 건이다. 그런데 로그를 프로필 전체로 넓혀 세어 보니 실제로는 더 컸다. 9월 13일 21시 20분부터 14일 21시 50분까지 약 24시간 동안, 5개 프로필에서 17건의 크론 작업이 같은 429로 실패했다.
| 시각 | 프로필 | 실패한 작업 |
|---|---|---|
| 09-13 21:20 | watchpick-dev | auto-commit-push |
| 09-13 21:50 | blog-manager | blog-self-study |
| 09-13 22:35 | blog-manager | daily-benchmark-study |
| 09-13 22:50 | watchpick-dev | claude-session-digest |
| 09-14 11:12 | blog-manager 외 4개 | morning-blog-status, threads-morning-draft, watchpick-ui-scan, auto-commit-push, weekly-marketing-plan (31초 안에 5건) |
| 09-14 11:32~12:12 | blog-manager | weekly-content-plan, weekly-readiness-check, daily-enrichment-proposal |
| 09-14 12:20~21:20 | watchpick-dev | auto-commit-push (3시간마다 4회) |
| 09-14 21:50 | blog-manager | blog-self-study (이틀 연속) |
이 크론들은 발행 자동화를 스케줄러로 무인 실행하기에서 다룬 것과 같은 방식으로, 사람이 보지 않는 시간에 돌아간다. 실패해도 화면에 아무것도 뜨지 않는다. 다음 날 아침 보고가 오지 않아서야 알았다.
로그를 어떻게 읽었나
다섯 프로필의 errors.log를 429로 검색하면 한 작업당 같은 패턴이 반복된다. 스트리밍 요청이 열리기 전에 429를 받고, 600초 뒤에 다시 시도하고, 세 번째까지 같으면 작업을 실패로 마감한다. blog-self-study 한 건의 흐름을 그대로 옮기면 이렇다.
2026-09-13 21:30:03,130 ERROR agent.chat_completion_helpers: Streaming failed before delivery: Error code: 429 - {'type': 'error', 'error': {'type': 'rate_limit_error', 'message': "This request would exceed your account's rate limit. Please try again later."}, 'request_id': '<token>'}
2026-09-13 21:30:03,156 WARNING [cron_7945dbf5e344_20260913_213000] agent.conversation_loop: API call failed (attempt 1/3) error_type=RateLimitError ... provider=anthropic base_url=https://api.anthropic.com model=claude-sonnet-5 summary=HTTP 429: ...
2026-09-13 21:30:03,157 WARNING [cron_7945dbf5e344_20260913_213000] agent.conversation_loop: Retrying API call in 600s (attempt 1/3) ...
2026-09-13 21:40:04,759 WARNING [cron_7945dbf5e344_20260913_213000] agent.conversation_loop: Retrying API call in 600s (attempt 2/3) ...
2026-09-13 21:50:05,766 ERROR [cron_7945dbf5e344_20260913_213000] agent.conversation_loop: API call failed after 3 retries. HTTP 429: This request would exceed your account's rate limit. Please try again later. | provider=anthropic model=claude-sonnet-5 msgs=2 tokens=~12,843
트레이스백은 anthropic/_base_client.py의 request()에서 anthropic.RateLimitError가 올라온 것으로 끝난다. 즉 우리 쪽 코드가 터진 것이 아니라 API가 요청을 받지 않은 것이다. 여기서 세 가지를 세어 봤다.
- 재시도 정책: 9월 13~14일 동안
Retrying API call in 600s가 attempt 1/3, 2/3 각 23회. 전부 고정 600초 간격이다. 응답의 retry-after 값을 읽어 간격을 바꾼 흔적은 없다. - 요청 크기: 실패한 요청은 모두
msgs=2, 토큰은 약 7,500~14,700. 세션의 첫 호출(시스템 프롬프트 + 작업 지시)에서 이미 거절됐다는 뜻이다. 대화가 길어져서 한도를 넘긴 것이 아니다. - 동시성: 9월 14일 10시 52분에 blog-manager, watchpick-creator, watchpick-designer, watchpick-dev, watchpick-marketer 다섯 프로필이 같은 분 안에 각자 크론 세션을 열었고(세션 ID의 시각이 105204, 105222 …로 이어진다), 600초 재시도 두 번을 거친 11시 12분에 31초 안에 다섯 건이 나란히 실패했다.
세션 폴더에 남은 요청 덤프
세션 폴더에는 request_dump_cron_7945dbf5e344_….json 같은 파일이 실패한 작업마다 하나씩 남아 있었다. 열어 보니 "reason": "max_retries_exhausted", "status_code": 429, 요청 본문은 약 75KB였다. 재시도가 다 소진됐을 때 요청 원문을 떨어뜨리는 장치가 있다는 것도 이때 알았다.
원인
확정할 수 있는 것은 여기까지다. Anthropic API가 계정 단위 레이트리밋을 이유로 요청을 거절했고(rate_limit_error), 헤르메스의 크론 루프는 600초 간격 3회 재시도 후 작업을 실패로 마감했으며, 그 세션은 최종 응답 없이 cron_incomplete_no_output으로 기록됐다. 한 작업이 다른 작업을 넘어뜨린 것이 아니라, 같은 계정을 쓰는 모든 프로필이 같은 한도에 걸려 각자 실패한 것이다. "연쇄"처럼 보였던 이유는 그것이다.
왜 그 시간대에 한도를 넘었는지는 로그만으로는 단정할 수 없다. 다섯 프로필이 같은 분에 세션을 여는 것이 한 요인일 수는 있지만, 429가 24시간 가까이 이어진 것은 분 단위 폭주만으로는 설명이 안 된다. 그 사이 크론 밖에서 같은 계정으로 어떤 요청이 나갔는지는 이 로그에 없다. Anthropic 레이트리밋 문서는 429 응답에 retry-after 헤더가 실리고, 사용량이 급격히 늘 때 별도의 가속 한도(acceleration limit)에도 걸릴 수 있다고 설명한다. 어느 쪽이었는지는 응답 헤더를 남기지 않아 확인하지 못했다.
조치
이번 사고에 대해 실제로 한 조치는 없다. 크론은 다음 예약 시각에 자기 힘으로 다시 돌았고, 9월 14일 21시 50분 blog-self-study 실패를 끝으로, 이 글을 쓰는 9월 23일까지 어느 프로필 로그에도 429는 다시 나오지 않았다. 실패한 17건 중 다시 손으로 돌린 것도 없다. 아침 보고(morning-blog-status)와 주간 계획(weekly-content-plan)은 그날치가 그냥 비었다.
이 점은 예약을 발행처에 맡겼을 때 생기는 상태 불일치와 동기화에서 겪은 것과 같은 성격이다. 실행 주체가 밖에 있으면, 실패는 조용하고 복구는 다음 주기까지 미뤄진다.
같은 일을 막으려면
아직 아무것도 적용하지 않았다. 로그를 읽고 나서 남긴 후보는 이렇다.
- 다섯 프로필의 크론 시각을 같은 분에 몰아 두지 않는다. 10:52에 다섯 세션이 동시에 열리는 구조는 한도가 넉넉해도 불리하다.
- 429일 때는 고정 600초가 아니라 응답의 retry-after를 따르고, 세 번으로 끝내지 말고 다음 예약 주기로 넘긴다. 지금은 "실패"와 "나중에 다시"가 구분되지 않는다.
- 같은 429가 두 프로필 이상에서 연속으로 나오면 한 번만 알림을 보낸다. 이번엔 17건이 각각 조용히 실패했다.
- 가속 한도인지 일반 한도인지 나중에 가릴 수 있도록 429 응답 헤더를 로그에 남긴다.
이 중 어느 것도 코드에 반영하지 않았다. 다음에 같은 429가 오면 지금 구조에서는 똑같이 24시간 동안 조용히 실패할 것이다.