Claude Code의 사용량 제한 자동 재개 방지 주체는 Remote Control이었다
요약
Claude Code의 Remote Control 세션에서 사용량 제한 자동 재개 기능에 문제가 있다는 내용을 다룹니다. 이 문제를 해결하기 위해 `Stop`과 `StopFailure` hook을 활용하여 수동으로 세션을 제어하는 방법을 제시하며, 관련 가드(guard) 설치 스크립트를 공유합니다.
핵심 포인트
- Remote Control 환경에서는 사용량 제한 초과 시 자동 재개가 작동하지 않습니다.
- 해결책으로 `asyncRewake`와 `exit 2`를 사용하여 수동 세션 재개를 구현해야 합니다.
- 사용량 추적 및 중지 기능을 추가한 가드(guard) 설치 스크립트가 제공됩니다.
요약 (TL;DR)
- 사용량 제한 리셋 후 작업 지속하기
자동 계속(autoContinueAtUsageLimit) 기능은 Remote Control 세션에서는 자동으로 시작되지 않습니다. 제가 찾아본 범위 내에서는 interactive-mode 페이지에만 설명되어 있으며, 화면에도 아무것도 표시되지 않았습니다. 제 기록을 확인해보니, Remote Control에 연결된 상태에서 상한에 도달했을 때 10번 중 1회도 시작되지 않았고, 시작하지 않은 8번은 모두 시작되었습니다. - 해결책은
Stop과StopFailurehook입니다.asyncRewake로 리셋을 기다리고, 내장(組み込み)이 재개되지 않았다면exit 2로 세션을 발생시킵니다. - 같은 스크립트에서 주간 사용량 85% 도달 시 중지하는 기능과 프롬프트에 사용량을 추가하는 것도 구현했습니다. - 확인한 것은 실제 대화 세션을 사용한 프로브(probe) 2개와 유닛 테스트 35건입니다.
실제 5시간 리셋, 주간 85% 이상 동작, 실제rate_limit의StopFailure는 실기에서는 시도하지 않았으며, 유닛 테스트(가짜 데이터)로만 통과시켰습니다.
작동 확인: Claude Code 2.1.289, macOS, Max 플랜.
한 번에 적용하기
이 글의 메커니즘은 ryoshumei/claude-code-usage-guard에 정리했습니다. Claude Code에 다음 문구를 그대로 붙여넣으면 dry run 계획을 보여주고, OK를 받은 후에 설치합니다.
https://github.com/ryoshumei/claude-code-usage-guard의 usage-guard를 설치해 주세요.
1. mktemp -d로 만든 임시 디렉토리에 git clone --depth 1을 하고, 그 안에 cd 합니다.
2. ./install.sh --dry-run을 실행하여 계획을 보여주고, 제가 OK라고 할 때까지 기다립니다.
...
직접 실행하려면:
git clone https://github.com/ryoshumei/claude-code-usage-guard ~/.claude/usage-guard-src && ~/.claude/usage-guard-src/install.sh
건드리는 파일은 모두 백업을 하고, 기존 statusline은 래핑만 할 뿐 표시가 변하지 않습니다(jq도 필요 없습니다). ~/.claude/usage-guard/install.sh --uninstall로 원래대로 되돌릴 수 있습니다. 아래는 제가 수동으로 구성한 원래 구조를 사용한 메커니즘과 이유 설명입니다.
증상
h카톤용 프로젝트의 86개의 티켓을 지휘자 모드(Opus의 메인 세션이 Sonnet의 서브 에이전트를 병렬로 실행하는 구성. 설정)로 돌리던 3월 10일 밤이었습니다. /remote-control을 넣었던 그 실행이 세션 상한에 도달했고, 자동 계속은 시작되지 않았으며, 리셋 시간인 23:40에 수동으로
Remote Control 페이지에는 없습니다. 제가 가지고 있는 transcript에서도 동일했습니다. 10분 이내의 기록을 1건으로 계산하면 총 29건이 있었는데, Remote Control에 연결되어 있을 때(2.1.2632.1.288)는 10건 중 0건이었고, 연결되어 있지 않았을 때(2.1.2782.1.288)는 8건 중 8건으로 자동 연속이 시작되고 있습니다. 10건 중 6건은 /remote-control is active 기록이 있었으며, 오래된 4건(9/8~9/12)은 bridge-session 기록으로 판단했습니다. 나머지 11건(claude -p 5, Claude Desktop 1, 월별 spend limit 3, 모델별 상한 1, 기능 추가 전 버전 1)은 계산에 포함하지 않았습니다.
하나의 세션에서 전환된 예가 가장 이해하기 쉽습니다. /remote-control을 사용하기 이전 3번(9/25 02:51, 9/25 07:01, 9/26 08:55)은 시작되었으나, 사용한 이후의 3번(12:57, 16:23, 19:06)은 시작되지 않았습니다. 서브 에이전트 수로 나뉜 것이 아니라, Remote Control의 유무에 따라 구분되었습니다. CLI 코드에서도 Remote Control 연결 중일 경우 대기하는 함수가 즉시 return합니다(2.1.289 번들에서 확인. 내부 구현이라 변경될 수 있습니다).
Remote Control은 /config의 'Enable Remote Control for all sessions'를 통해 모든 세션에 자동으로 연결할 수 있습니다(문서 참조). 사용자가 직접 /remote-control을 입력하지 않아도, 연결되어 있을 수 있습니다.
해결책 (対策)
내장된 자동 연속 기능을 활성화 상태로 유지하고 그 위에 훅(hook)을 추가했습니다. autoContinueAtUsageLimit은 user settings에 true로 명시해 두었습니다(없으면 프로젝트 설정 파일에 이 키가 존재하는 것만으로 기능이 중단됩니다. 설정 참조).
훅은 4개, 스크립트는 1개(~/.claude/hooks/usage-guard/usage_guard.py)입니다.
| hook | 실행하는 명령어 | 역할 |
|---|---|---|
Stop | resume | 5시간 주기가 100%로 끝나도, 리셋될 때까지 기다렸다가 세션을 재개합니다. |
StopFailure (rate_limit) | resume | 상한에 도달하여 트랜잭션이 종료되면, 마찬가지로 기다렸다가 재개합니다. |
UserPromptSubmit | prompt | 프롬프트마다 사용량을 누적 계산합니다. 주간 사용량이 85% 이상이면, 내장된 연속 프롬프트를 차단합니다. |
PreToolUse (Agent, Task, Workflow, SendMessage) | gate | 주간 사용량이 85% 이상일 경우, dispatch나 기존 워커에게 작업을 요청하기 전에 확인 절차를 거칩니다. |
resume은 리셋 후 4분 뒤에 transcript를 확인합니다. 내장된 자동 연속이거나 제가 먼저 재개했다면 아무것도 하지 않습니다. Esc로 중지한 대기 상태도 재개하지 않으며, 내장이 대기를 시작했다면 리셋부터 최대 20분까지 기다립니다. 그래도 아무도 재개하지 않으면, 주간 사용량이 85% 미만임을 확인하고 stderr에 메시지를 기록하며 exit 2로 종료합니다. asyncRewake: true의 hook이 exit 2로 끝나면, 유휴(idle) 세션도 해당 메시지와 함께 깨어납니다(hooks 문서 참조). 두 이벤트 모두에 이 기능을 적용한 이유는, 상한에 도달하여 트랜잭션이 끊기면 StopFailure가 되고, 그 전에 종료되면 일반적인 Stop이 되기 때문입니다. 동일한 리셋을 기다리는 것이 하나만 되도록, 세션과 리셋 시간별 마커로 범위를 좁혔습니다.
~/.claude/settings.json의 해당 부분입니다(다른 hook은 생략했습니다).
{
를 호출합니다 (macOS에서는 Xcode Command Line Tools가 필요합니다). 스크립트와 테스트에는 Python 3.9 이상이 필요합니다. `resume`
의 `timeout`
이 86400(24시간)인 이유는, `asyncRewake`
의 hook에는 `timeout`
이 적용되기 때문입니다. `prompt`
와 `gate`
의 끝에 붙은 `|| true`는 스크립트가 종료되었을 때를 위한 보험 장치입니다. 파일이 없으면 Python은 종료 코드 2로 끝나는데, 문서에 따르면, `UserPromptSubmit`
의 exit 2는 프롬프트를 중지시킵니다. `|| true`가 없다면 모든 프롬프트가 멈춥니다. `resume`
에 붙이지 않는 이유는, exit 2가 기상 신호이기 때문입니다.
사용량은 statusline에 전달되는 JSON의 `rate_limits` (Pro와 Max만)에서 가져옵니다. 전제 조건은 `jq`와 settings.json의 `statusLine`의 command입니다(문서 참조). 다음은 제 `~/.claude/statusline-command.sh`의 시작 부분입니다. 이미 `input=$(cat)`이 있는 스크립트라면, 이 줄을 덮어쓰지 말고 나머지 줄들을 그 직후에 추가해 주십시오 (두 번째 로딩 시 stdin이 비고 statusline이 비게 됩니다). 기본 statusline은 다른 글에 작성했습니다.
input=$(cat)
Cache the plan's rate limits (weekly %, 5-hour %) so a long-running session
can pause itself at a threshold. Account-wide, so any session's value will do.
...
숫자는 계정 전체의 것이므로, 어떤 세션이 업데이트되어도 같은 값이 됩니다. 모델에게는 `~/.claude/CLAUDE.md`에서 최종 답변 끝에 사용량을 1줄 추가할 것(메인 세션만)과, 주간 85%에서 자율 작업을 중지하도록 전달했습니다.
추가할 때는 다음 전체 내용을 `~/.claude/hooks/usage-guard/usage_guard.py`에 저장하고, 위에 있는 두 부분을 합치세요.
## usage_guard.py 전문(383행)
#!/usr/bin/env python3
"""usage-guard: keep a weekly reserve, and resume sessions after the 5-hour limit resets.
Reads ~/.claude/rate-limits.json, which ~/.claude/statusline-command.sh writes from the
...
## 작동 여부 확인하기
`usage` 명령어는 캐시된 숫자와 판정 결과를 보여줍니다.
python3 ~/.claude/hooks/usage-guard/usage_guard.py usage
weekly 54% (resets 10/08 05:00) · 5-hour 12% (resets 21:10) · read 1 min ago · stop line 85% → ok
`read 1 min ago`는 캐시가 오래된 시간을 의미합니다. 캐시가 없으면 `no reading yet`이 표시되고, 종료 코드는 1이 됩니다(statusline의 3줄이 작동하지 않음). 중지 판정 결과를 보고 싶을 때는 임계값을 현재 사용량(저는 54%)보다 낮춥니다.
USAGE_GUARD_WEEKLY_STOP=50 python3 ~/.claude/hooks/usage-guard/usage_guard.py usage
이렇게 하면, 줄 끝이 `stop line 50% → STOP autonomous work`가 됩니다.
로그는 `~/.claude/usage-guard/log`입니다. 시간과 세션 ID의 앞 8자리에 이어서, `armed via …` (리셋 대기 시작), `woke the session` (기상), `already resumed …` 등이 기록됩니다.
(재개됨으로 보고 건너뛰기(再開済みで見送り) 등이 한 줄씩 남습니다. 파일은 첫 번째 이벤트에서 생성되므로, 아직 아무 일도 일어나지 않은 제 환경에는 없습니다.
유닛 테스트는 35건이며, 가짜 캐시와 임시 디렉토리만 사용하고 실제 캐시나 로그에는 접근하지 않습니다.
bash ~/.claude/hooks/usage-guard/usage_guard.test.sh
마지막에 `passed 35, failed 0`
와 출력됩니다(약 38초).
## usage_guard.test.sh 전문 (164행)
#!/usr/bin/env bash
Tests for usage_guard.py. Uses a fake cache and state dir; never touches the real ones.
set -u
...
`asyncRewake`
으로 실제로 발생시킬 수 있는지 2번의 실제 대화 세션에서 확인했습니다. 20초 기다린 후 stderr에 메시지를 출력하고, `exit 2`
하는 hook을 `Stop`에 붙인 경우와, 존재하지 않는 모델(`model_not_found`)
로 `StopFailure`를 일으킨 경우입니다. 둘 다, hook이 끝나고 1초 이내에 새로운 턴이 시작했고, 응답은 그 1~2초 후였습니다(`StopFailure`의 경우는 모델이 없기 때문에 같은 에러입니다).
## 주의사항
- **실제 기기에서 테스트하지 않은 것**: 실제 5시간 리셋을 최종 버전의 hook으로 실행하는 것, 주간 85% 이상의 동작(지속 프롬프트의 차단, `gate` 확인, 리셋 후에도 멈춰 있는 것), 실제 usage limit의 `StopFailure` 입력을 유닛 테스트의 가짜 데이터로만 진행했습니다. Esc로 멈춘 대기를 존중하는 처리도 실물 transcript에서는 확인하지 못했습니다(`Automatic continue cancelled`라는 문구는 문서와 2.1.289 번들에 있습니다). - **85%의 제동은 부분적**: 설계상(실제 기기에서는 미확인), 지속 프롬프트와 hook에 의한 깨어남을 막고, dispatch나 기존 워커로의 계속 요청 전에 확인 과정을 거칩니다. `Continue the task you were working on when the limit was reached`를 맨 앞쪽에 포함하는 짧은 프롬프트는 지속으로 간주하여 막습니다. 그 문장을 인용한 것만으로 된 긴 보고서는 통과합니다. dispatch하지 않는 긴 턴이나 `/loop`의 프롬프트는 모델이 `CLAUDE.md`의 지침을 따르는지에 달려 있습니다. - **숫자의 신선도**: 캐시는 어느 세션의 statusline이 업데이트될 때만 다시 쓰여집니다(`refreshInterval`은 미설정입니다). `read N min ago`로 오래되었는지 알 수 있습니다. `UserPromptSubmit`는 서브 에이전트의 보고에서도 실행되므로, 지휘자 모드에서는 줄이 여러 번 들어갑니다. - **내부 구현에 의존하는 부분**: 코드상의 판별(Remote Control일 때는 대기를 시작하지 않음, 2.1.289 번들의 조기 return)과, `claude -p`로 `asyncRewake`의 hook이 포그라운드에서 실행되는 것은 그 코드를 읽어본 결과이며, 문서에는 없고 버전에 따라 달라집니다. 내장된 재개는 제 로그 상에서는 리셋 후 1~2분 뒤였습니다(7건 중 71~99초). 문서는 `StopFailure`의 출력을 무시한다고 쓰지만, `asyncRewake`의 hook에서는 발생시킬 수 있었습니다(관측한 동작입니다). - **사소한 주의**: 깨어남은 UI 상에서 `Stop hook feedback`으로 표시됩니다(겉모습만 그렇습니다). 대기 중에 Claude Code를 종료하면 재개되지 않습니다. 주간 한도는 자동으로 재개되지 않습니다.
## 동작 확인한 버전
- Claude Code 2.1.289: 프로브, 코드 확인, 유닛 테스트
- 히스토리 집계: 2.1.219~2.1.288의 transcript (29 에피소드)
- claude.ai Max 플랜, macOS,
`/usr/bin/python3`
3.9.6, jq 1.7.1 - 미확인: macOS 외, IDE 확장, Desktop 앱
## 참고 링크
### 토론

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