
Claude Code의 「court 무한 루프」로부터 대화를 버리지 않고 부활하기 (Opus 4.8)
요약
Claude Code Opus 4.8 업데이트 이후 발생하는 'court' 단어 출력 및 툴 호출(Tool Call) 무한 루프 현상과 그 해결 방안을 다룹니다. 기존의 세션 초기화 방식 대신 대화 문맥을 유지하며 문제를 해결하는 방법을 제시합니다.
핵심 포인트
- Opus 4.8에서 툴 호출 시 'court' 등 불필요한 단어가 붙으며 파싱 오류 발생
- 망가진 출력이 대화 이력에 남으면 모델이 이를 학습하여 무한 루프 발생
- 세션을 버리지 않고 대화 문맥을 유지하며 문제를 해결하는 전략 필요
Opus 4.8로 업데이트한 이후, Claude Code가 court나 count 같은 알 수 없는 단어를 내뱉으며 툴 호출 (Tool Call)이 멈추는 현상 ―― 이런 일을 겪고 계시지는 않나요?
게다가 더 골치 아픈 점은, 대처법으로 돌아오는 답변이 대개 "세션을 바꿔라"라는 것입니다. 그런 건 알고 있습니다. 하지만 저는 지금까지 공들여 쌓아온 대화를 어떻게든 이어가고 싶은데, 그 이어가기 명령을 입력해도 court 때문에 멈춰버리니 곤란한 겁니다. 새로운 세션으로 바꾸면 지금까지의 문맥 (Context)은 전부 잊혀집니다. 대화를 이어가는 것이 중요하다는 건 알지만, 또 거기서 court가 나옵니다.
어쩔 수 없이 포기하고 세션을 바꿔서 똑같은 명령을 다시 내리면, 거기서 또 court가 나옵니다…….
솔직히 짜증이 납니다. Claude 사용을 그만둘까 하는 생각이 들 정도로 말이죠. 게다가 저는 대화를 "진행하고" 싶은데, 세상의 대처법은 "대화를 그만둬라", "세션을 바꿔라"뿐입니다. Opus 4.7로 낮춰라, 이력을 잊게 해라, 새 세션을 열어라 ―― 아니, 이해는 하지만 그건 전부 "지금까지의 대화를 버려라"라고 말하는 것과 같습니다.
제가 하고 싶은 것은 "Opus 4.8을 사용한 상담"입니다. 세션이 망가졌거나 문맥이 가득 찼다면 어쩔 수 없다고 칩시다. 하지만 그렇다면 적어도 제대로 "이어가기를 완료"하고 싶습니다. 무엇을 위해 유료 결제를 하며 Opus 4.8을 사용하고 있는가 하는 문제죠. 그래서 제가 간절히 원했던 것은 결국 다음 두 가지뿐입니다.
- 애초에 court를 내뱉지 않게 하고 싶다, 나오더라도 멈추지 않게 하고 싶다 (어쨌든 진행하고 싶다)
- 나오더라도 루프에 빠지지 않고, 적어도 "마지막 하나의 명령"만은 통과시키고 싶다 (대화를 버리지 않고)
같은 생각을 하는 사람이 분명 있을 거라 믿고, 이 두 가지 ―― 대화를 버리지 않고 헤쳐 나가기 ―― 를 주축으로 정리했습니다. "4.7로 낮추기", "이력 삭제하기"와 같은 "대화 버리기 계열"의 회피책은 어쩔 수 없는 상황에서의 최종 수단으로서 가장 아래에 배치했습니다.
우선, court로 인해 멈추는 상태는 이렇습니다
구체적으로 어떤 일이 일어나고 있냐면, 파일을 읽거나 명령을 실행하는 "툴 호출 (Tool Call)"이 다음과 같이 망가진 채 출력됩니다.
court
<invoke name="Read">
<parameter name="file_path">C:\Users\...\CLAUDE.md</parameter>
...
본래는 내부 명령으로서 그대로 실행되어야 할 것에, 선두에 court (또는 count, call)라는 불필요한 단어가 붙고, 감싸는 태그도 깨져 있습니다. 이를 하네스 (Harness)가 "파싱할 수 없음 (malformed)"이라고 판단하여, 아무것도 실행되지 않은 채 멈추는 것입니다.
화가 나는 점은, 여기서 "왜 멈추는 거야, 움직여봐"라고 재촉해도, "방금 이런 명령을 내렸는데 이런 식으로 멈춰 있습니다. 제대로 작동시켜 주세요"라고 캡처와 함께 정중하게 설명해도, 그 답변이 다시
court
<invoke name="Read">
<parameter name="file_path">C:\Users\...\CLAUDE.md</parameter>
...
"아, 또냐!"라며 가볍게 책상을 치고 싶어집니다. 거짓말입니다, 저는 여러 번 쳤습니다.
게다가 이것은 한 번으로 끝나지 않습니다. 망가진 출력이 대화 이력에 남으면, 모델은 그것을 "올바른 작성 방식"이라고 착각하여 계속 흉내 냅니다. 이것이 몇 번을 다시 시도해도 같은 지점에서 멈추는 "court 무한 루프"의 정체입니다.
다만, 사실 court가 망가뜨리는 것은 어디까지나 "툴 호출 (Tool Call)"일 뿐이며, 일반적인 문장은 제대로 돌아옵니다. 이게 무슨 뜻일까요? 이것이 대화를 버리지 않고 헤쳐 나가기 위한 탈출구가 되므로 순서대로 설명하겠습니다.
이것은 데스크톱 앱도 CLI도 모든 환경에서 발생합니다, Opus 4.8인 한
처음에는 "데스크톱 앱 (Code 탭)만의 문제인가"라고 생각했지만, 아니었습니다. 이것은 모델이 툴 호출을 생성하는 순간에 발생하기 때문에 사용하는 환경을 가리지 않습니다. 데스크톱 앱이든, 터미널의 CLI (claude)든, WSL 상의 Claude Code든 조건이 갖춰지면 발생합니다 (저 자신도 WSL 등에서 동일한 일이 일어나는 것을 확인했습니다).
그러한 사정이기 때문에, 이 기사의 대처법은 우선 환경을 불문하는 방법을 중심으로 소개하겠습니다. 터미널에서만 사용하는 명령어(예: --continue 등)가 나올 때마다 그때마다 「CLI의 경우」라고 명시할 것이므로, 데스크톱 앱만 사용하는 분들도 「나와는 상관없는 명령어뿐이다」라고 느끼지 않고 읽으실 수 있을 것입니다.
대화를 버리지 않고 헤쳐 나가는 3단계
여기서부터가 본론입니다. 사고방식은 간단합니다. 「court를 지우는 것」이 아니라 「대화를 남긴 채로 어떻게든 앞으로 나아가는 것」을 최우선으로 합니다. court가 나타나더라도 작동만 하면 됩니다. 위에서부터 순서대로 시도하며, 가능한 한 높은 단계에서 버텨주세요. 아래로 내려갈수록 대화를 포기하는 정도가 커집니다.
| 하고 싶은 것 | 방법 | 대화의 보존 방식 |
|---|---|---|
| 나타나도 마지막 1개는 통과시키기 | 사과하게 하지 않고, 툴 호출 (tool call)만 재전송 | 그대로 남음 |
| ... | /compact로 요약하여 압축 | 요약본으로 남음 |
| 도저히 안 될 때 | 인수인계를 만든 후 새 세션 시작 | 일단 포기 |
① 나타나더라도, 마지막 1개만은 통과시키기
가장 효과적이면서도 잘 알려지지 않은 방법입니다. 게다가 대화를 단 1mm도 버리지 않습니다.
흔히 「한 번 망가지면 리트라이(retry)해도 소용없다」라고 말하지만, 이는 절반만 사실입니다. 「설명하는 문장을 쓴 다음 다시 시도하기」는 확실히 고쳐지지 않지만, 「문장을 일절 쓰지 않고, 멈춘 툴 호출 (tool call)만 그대로 다시 보내기」를 하면 꽤 높은 확률로 통과합니다.
이유는 명확합니다. 무너지는 트리거가 툴 호출 (tool call) 직전에 놓인 문장이기 때문입니다. 「죄송합니다, 다시 한번 실행하겠습니다」라고 서두를 쓰면, 그 직후의 시작 태그가 어긋나면서 다시 court로 변해버립니다. 서두만 없다면 시작 태그로부터 순순히 출력해 줍니다.
따라서 당신이 해야 할 일은 「친절한 설명을 그만두는 것」입니다. 멈췄을 때 긴 지시를 추가하지 말고, 「court」, 「멈춤」, 「다시 한번」 정도의 짧은 한마디만 돌려주세요.
「멈춰 있어요. 왜 그런가요?」라고 묻거나 「또 멈췄는데 어떻게 된 건가요?」, 「적당히 좀 하세요」라고 하면 Claude는 「죄송합니다」부터 시작하려고 시도하다가 「court」가 되는 것입니다.
화가 치밀어 오르는 것은 이해합니다. 저도 몇 번이나 화가 났습니다. 하지만 명령상으로는 「멈춤」 이것뿐입니다. 여기서 참아야 합니다.
나중에 소개할 hook이나 CLAUDE.md를 넣어두면, Claude는 사과하는 문장을 쓰지 않고 툴 호출 (tool call)만 다시 내보냅니다. 좋게 생각해서 상황을 자세히 설명해 주는 것이, 이 증상에 있어서는 오히려 역효과입니다.
② 연쇄적으로 통과되지 않는다면, 망가진 이력만 버리기
①번으로 버티기 어려워지면, 망가진 출력이 이력에 여러 개 쌓여 있는 상태가 됩니다. 그렇다고 해서 갑자기 대화를 전부 버릴 필요는 없습니다. /compact를 사용하면 대화 내용은 요약으로서 남겨둔 채, 무한 루프의 연료가 되고 있는 망가진 이력만 덜어낼 수 있습니다. /clear가 전부 지우는 것이라면, /compact는 요점을 남기는 중간책이라고 생각하세요. 이것은 CLI에서도 데스크톱 앱에서도 /compact라고 입력하면 사용할 수 있습니다.
물론 슬래시 명령어(slash command)에 익숙하지 않은 분들도 있을 것입니다. 그럴 때는 무리하게 명령어를 사용하지 않아도 괜찮습니다. 일본어로(또는 한국어로) 「여기까지의 대화를 요약해서, 망가진 부분은 제외하고 다시 정리해 주세요」라고 부탁하는 것만으로도 요점을 남긴 채 재정비하는 계기가 됩니다. 망가진 것이 최근 몇 턴뿐이라면, 거기까지 되돌리는 것도 방법입니다. 터미널이라면 Esc를 두 번 눌러 되돌리고, 데스크톱 앱이라면 되돌릴 수 있는 조작이 있다면 그것으로 되돌리고, 없다면 다음 ③번으로 넘어갑니다.
③ 도저히 되돌아오지 않는다면, 버리기 전에 인수인계 만들기
①도 ②도 안 되고, 무한 루프에서 빠져나올 수 없다. 그때만 새로운 세션으로 옮깁니다. 솔직히 말하면, 여기서부터는 「대화를 포기하는」 쪽입니다. 4.7로 버전을 낮추거나, 이력을 지우거나, 새 세션을 여는 것 ―― 모두 court는 사라지지만, 쌓아온 문맥(context)도 함께 사라집니다. 하고 싶은 일이 아니라, 마지막 타협이라고 생각하십시오.
그렇기에 버리기 전에 한 가지 수고를 더합니다. 여기서 「court가 망가뜨리는 것은 툴 호출 (tool call)뿐이며, 문장은 망가지지 않는다」는 성질이 힘을 발휘합니다. 멈춘 세션이라도, 문장으로라면 제대로 된 답이 돌아오기 때문입니다.
주의해야 할 점은, 한 줄 요약만으로는 부족하다는 것입니다. 대화가 길어지면 요점만 적은 짧은 메모를 새 세션에 전달하더라도, 정작 중요한 "지금까지 해온 일"이 빠져서 제대로 이어지지 않습니다. 그래서 요청할 때는 구체적으로 지정합니다. 예를 들어 "이 작업의 목적, 전제 조건, 지금까지 결정된 사항, 수정한 파일과 변경 내용, 아직 남아 있는 작업, 막혔던 점을 새로운 채팅으로 인계한다는 전제하에 가능한 한 자세히 정리해 주세요"라고 쓰면, 인계에 적합한 기록이 문장으로 돌아옵니다. 그것을 복사해서 새로운 세션의 맨 처음에 붙여넣은 뒤 재개합니다.
"그런 건 파일에 저장해 두면 되는 것 아닌가"라고 생각할지도 모릅니다. 맞습니다만, 까다로운 점은 그 저장(이 또한 도구 호출 (tool call)입니다)이 court에서 멈춰버린다는 것입니다. 즉, "파일에 써 두면 안전하다"라는 당연한 대책이, 정작 중요한 쓰기 순간에 깨져버립니다. 따라서 순서를 바꿔서, 먼저 문장으로 뱉어내게 한 뒤 그것을 직접 파일에 붙여넣거나 새로운 세션 측에서 저장하게 하는 것이 현실적인 절차입니다.
새로운 세션으로 옮길 때 주의할 점은 딱 하나, 망가진 세션을 "계속하지" 않는 것입니다. 터미널이라면 /clear로 리셋하거나 claude를 다시 실행하겠지만, 이때 --continue (-c)나 --resume은 붙이지 마세요. 이것들은 이전의 망가진 이력을 다시 읽어오는 플래그이므로, 어렵게 새로 시작해도 또 같은 지점에서 멈추게 됩니다. 데스크톱 앱 사용자는 --continue 같은 명령어를 입력할 일이 없습니다. 망가진 채팅을 다시 열지 않고, 인계용 문장을 가지고 새로운 채팅을 시작한다고만 기억하면 됩니다.
..참고로 저는 일단 이력으로 남겨두면서도 제목을 "【망가짐】" 등으로 설정해 둡니다. 이것이 좋은 방법인지는 차치하고 말이죠.
애초에 court가 나오지 않게 하기
여기까지는 "나와버린 이후"의 이야기였습니다. 하지만 매번 이걸 수동으로 하는 것은 소모적입니다. 이상적인 것은 애초에 멈추지 않는 것입니다. ①에서 보았듯이, 멈추는 트리거는 "도구 호출 전에 문장을 쓰는 것"이었습니다. 그렇다면 Claude가 매번 그 서문을 쓰지 않도록 유도하면 됩니다. 그것을 자동으로 수행하는 것이 UserPromptSubmit hook입니다.
처음에는 이 회피책을 CLAUDE.md나 메모리에 적어 두었습니다. "도구를 사용할 때는 서문 문장을 쓰지 않고 도구 호출부터 시작할 것", "한 번의 응답에는 도구를 하나만 사용할 것", "무거운 도구는 나누어서 호출할 것" 같은 내용입니다. 적어둔 내용은 맞지만, 세션을 넘어가면 재발했습니다. CLAUDE.md나 메모리는 대화 시작 시점에 한 번 읽힐 뿐이라서, 대화가 진행됨에 따라 의식이 흐려져 다시 서문을 써버리는 것입니다. 지시는 "부탁"이지 "강제"가 아니라는 뜻이죠.
그래서 사고방식을 바꿨습니다. "읽어서 기억하게 하는 것"을 그만두고, "매 턴 기계적으로 삽입하는 것"으로 결정했습니다. Claude Code의 UserPromptSubmit hook은 사용자가 입력할 때마다 발화하여, 입력한 내용을 Claude의 컨텍스트(context)에 주입해 줍니다. 즉, 주의 사항을 매 턴 강제적으로 눈앞에 놓을 수 있습니다. 대화 시작 시 한 번뿐인 CLAUDE.md와 달리, 이것은 흐려지지 않습니다.
설정은 폴더에 셸 스크립트(shell script)를 하나 두고, 그것을 호출하도록 등록하기만 하면 됩니다. ~/.claude/settings.json을 수정할 수 있는 환경이라면 CLI에서도, WSL에서도, 데스크톱 앱에서도 작동합니다 (저는 데스크톱 앱에서도 작동하는 것을 확인했습니다). 셸에 익숙하지 않다면 이 장은 건너뛰어도 좋으며, 앞부분의 대처법만으로도 충분히 유용합니다.
먼저 ~/.claude/hooks/court-guard.sh를 만듭니다.
#!/bin/bash
# UserPromptSubmit hook: 도구 호출 malformed (court/count/call 혼입 + 접두사 누락) 대책을 매 턴 주입함
cat <<'JSON'
...
실행할 수 있도록 설정합니다.
chmod +x ~/.claude/hooks/court-guard.sh
마지막으로 ~/.claude/settings.json에 등록합니다.
{
"hooks": {
"UserPromptSubmit": [
...
]
}
}
이렇게 하면 매 턴, Claude가 답변을 쓰기 시작하기 직전에 "서론을 쓰지 마라, 도구(tool)는 하나만 사용해라, 멈추면 도구만 다시 출력해라"라는 주의 사항을 보게 됩니다. 체감상으로는 멈추는 횟수가 확실히 줄어들었습니다.
한 가지 오해하기 쉬운 점을 적어둡니다. 이것은 hook을 통해 malformed(형식 오류)를 "차단"하는 메커니즘이 아닙니다. 이러한 오류는 파싱(parsing) 전 단계에서 발생하기 때문에, PreToolUse hook은 발화조차 하지 않습니다(형식이 깨진 호출은 애초에 도구로 인식되지 않기 때문입니다). UserPromptSubmit hook이 하고 있는 일은, 생성할 때마다 "서론을 쓰지 않도록" 유도하여 트리거가 당겨지지 않게 하는 것입니다. 직접적인 방어가 아니라, 발생 확률을 낮추기 위한 운영 방식이라고 이해하는 것이 정확합니다.
그 외에 효과적이었던 것은 태스크(task)를 작게 나누어 상호작용을 짧게 유지하는 것과, /compact를 수시로 입력하여 이력(history)이 비대해지지 않게 하는 것입니다. Opus 4.7로 버전을 낮추는 방법도 있지만, 그것은 "대화를 진행하는" 것보다 "court를 없애는" 것을 위한 선택이므로, 저는 우선 hook과 짧은 상호작용, 그리고 ①번 방법으로 버티고 있습니다.
어떤 상황에서 발생하기 쉬운가
저의 관측과 여러 곳의 보고를 종합하면, 발생하기 쉬운 조건은 대략 정해져 있습니다. 문맥(context)이 크게 부풀어 오른 긴 세션, 특히 항상 켜두는 것과 같은 긴 대화. 도구 호출(tool call)의 인자(argument)에 일본어가 섞일 때. 도구를 연속으로 호출하거나, 무거운 인자를 가진 도구를 호출할 때. 이 요소들이 겹치면 발생 확률이 급격히 높아집니다.
만약을 위해 확인해 보았는데, 이것은 제 환경만의 불운이 아닙니다. GitHub의 claude-code issue에도 동일한 현상이 여러 건 올라와 있으며, "서론을 쓰면 깨지고, 도구 호출부터 시작하면 발생하지 않는다", "이전 버전에서는 나타나지 않는다", "긴 문맥에서 다발한다" 등 재현 조건까지 일치합니다. 즉, 이미 알려진 동작입니다. 다만, 모델 내부에서 실제로 어떤 일이 일어나고 있는지는 외부에서 단정할 수 없으므로, 여기서는 "내 환경에서는 이렇게 발생했고 이렇게 고쳤다"라는 이야기와 "이렇게 보고되고 있다"라는 이야기를 나누어 작성하고 있습니다.
마치며
중요한 것은 목적을 혼동하지 않는 것이라고 생각합니다. 우리가 하고 싶은 것은 "court를 없애는 것"이 아니라, "쌓아온 대화를 버리지 않고 앞으로 나아가는 것"입니다. 그래서 순서는 언제나 같습니다. 우선, 멈추더라도 문장을 쓰지 않고 도구 호출만 다시 보내서 마지막 하나를 통과시킨다. 그래도 안 된다면 /compact로 깨진 이력만 날린다. 도저히 안 될 때만, 인수인계 문장을 제대로 작성한 뒤 새로운 세션으로 옮긴다. 그리고 다음부터는 UserPromptSubmit hook으로 매 턴 서론을 금지하여 애초에 멈추지 않도록 해둔다.
"재시도(retry)는 무의미하다"라는 말은 절반만 사실입니다. 무의미한 것은 문장을 붙인 재시도뿐입니다. 문장을 제외하고 도구 호출만 보내면 통과됩니다. 그리고 court가 망가뜨리는 것은 도구 호출뿐이며, 문장은 무사하기 때문에 최악의 경우라도 인수인계 내용은 추출할 수 있습니다. 이 두 가지만 기억해도, 멈췄을 때 대화를 포기하지 않아도 됩니다. 같은 문제로 소모되고 계신 분들께 도움이 된다면 기쁘겠습니다.
마지막으로 한마디
여기까지 쓰면서 드는 생각은, "왜 이런 걸 사용자가 신경 써야 하는가" 하는 것입니다. 제대로 작동해주길 바라지만, 완전히 의존하고 있어서 벗어날 수 없다는 것은 AI 시대 특유의 고민인 것 같습니다.
얼마 전 Fable이 출시되자마자 금지되었던 것처럼, 모델에 따라 휘둘리고 AI에 의해 휘둘리는 것은 이제 어쩔 수 없는 일인가 봅니다.
Discussion

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