Yule Seo
2026.05.26 · 8 min read

오픈클로 claude-cli 컨텍스트 에러 트러블슈팅

지난 글에서 현재 나의 오픈클로 세팅에 대해 공유했는데, 최근 맞닥뜨린 Context Token 관련 에러에 대해 기록을 남겨보고자 한다.

한 줄로 증상을 정리하자면 이렇다.

오픈클로 세션 대화를 계속 이어 가다보면, 갑자기 토큰 수가 1.9M / 1M 이런 식으로 말도 안 되게 Overflow가 일어나버리고, 그 이후에 보내는 메시지는 ⚠️ Requested agent harness "claude-cli" is not registered. 라고 나오며 모두 에러가 나게 된다.

처음엔 하네스가 안 깔린 줄 알았다

에러 메시지를 곧이곧대로 읽으면 "claude-cli 하네스가 등록이 안 됐다"는 거니까, 처음엔 당연히 설치나 등록이 풀렸나 싶었다. 그래서 제일 만만한 것부터 했다. 게이트웨이 재시작.

bash
openclaw gateway restart

근데 안 된다. 몇 번을 다시 시작해도 그 세션에 메시지만 보내면 똑같이 harness가 없다고 한다. 사실 이때까지만 해도 업데이트가 꼬였나 하고 업데이트 다시 해보고 게이트웨이만 애꿎게 껐다 켰다 했다.

로그부터 켜고 메시지를 보내봤다

증상만 보고 추측해봐야 의미가 없으니, 로그를 따라가면서 실제로 메시지를 보내봤다.

bash
openclaw logs --follow

그랬더니 메시지를 보내는 순간부터 이 두 줄이 1초에 한 번씩 끝도 없이 찍히고 있었다.

text
error diagnostic  message processed: ... outcome=error
  error="MissingAgentHarnessError: Requested agent harness "claude-cli" is not registered."
error channels/telegram  [telegram][diag] spooled update ... failed; keeping for retry: MissingAgentHarnessError ...

알고 보니 텔레그램은 받은 메시지를 spool(큐)에 쌓아두고 재시도하는 구조라, 한 번 실패한 메시지가 큐에 박혀서 영원히 재시도하고, 그게 또 영원히 실패하면서 로그를 도배하고 있던 거였다. 채팅 화면에서는 아무 답도 안 오니까 조용한데, 뒤에서는 난리가 나 있던 셈.

그리고 이상한 게 하나 있었다. 크론잡은 멀쩡하게 잘 돌고 있었다. 채팅만 죽고 예약 작업은 산다? 이게 결정적인 힌트였다.

근데 직접 부르면 또 된다?

채널(텔레그램/웹챗) 말고 CLI로 에이전트를 직접 한 번 찔러봤다.

bash
openclaw agent --agent main --message ping --json
json
"executionTrace": {
  "winnerProvider": "claude-cli",
  "winnerModel": "claude-opus-4-7",
  "attempts": [{ "provider": "claude-cli", "result": "success" }],
  "fallbackUsed": false
}

result: success. 직접 부르면 claude-cli로 멀쩡하게 답을 한다. 그러니까 "claude-cli 하네스가 등록이 안 됐다"는 그 에러 메시지가, 사실 진짜 원인을 가리키는 게 아니었던 거다. 등록도 인증도 다 멀쩡한데 특정 경로, 그러니까 채널에서 대화가 길어졌을 때만 터지는 것이었다.

여기서부터 방향이 잡혔다. 등록이 깨진 게 아니라, 뭔가가 claude-cli를 "있어선 안 될 곳"에서 부르고 있다는 얘기.

진짜 원인: claude-cli는 "하네스"가 아니다

문서랑 소스를 좀 파보니 그림이 그려졌다. 오픈클로 문서를 보면 런타임을 두 갈래로 설명한다. 하나는 오픈클로 안에 내장된 임베디드 하네스(PI 같은 것)고, 다른 하나는 로컬 CLI를 그대로 돌리는 CLI 백엔드다. claude-cli는 후자고, claude 바이너리를 실제로 띄워서 내 Claude 구독으로 답을 받아온다. 그리고 결정적으로 이 한 줄이 있다.

claude-cli is not an embedded harness id and must not be passed to AgentHarness selection.

출처: OpenClaw 공식 문서, Agent runtimes

harness 선택에 넘기면 안 되는 걸, compaction 경로에서 떡하니 넘기고 있었던 셈이다.

문제는 컨텍스트가 꽉 찼을 때(overflow) 일어난다. 오픈클로는 대화가 길어지면 오래된 내용을 요약(compaction)하려고 하는데, 이 compaction을 돌리는 코드가 세션의 런타임(=claude-cli)을 하네스 목록에서 찾으려다가 못 찾고 MissingAgentHarnessError를 던졌던 거다. claude-cli는 하네스가 아니니까 그 목록에 있을 리가 없다.

그리고 한 가지 더 어이없는 사실이 있다. 그 1.9M / 1M 같은 토큰 수치 자체가 사실 뻥튀기된 허수다. claude-cli 세션은 실제 대화 맥락을 Claude Code(~/.claude) 쪽이 관리하고, 오픈클로가 들고 있는 트랜스크립트는 거의 비어 있다. 그래서 오픈클로가 보여주는 컨텍스트 %는 캐시까지 중복으로 더해진 부풀린 값이라 믿을 게 못 된다. 즉 진짜로 1M을 넘겨서 터진 게 아니라, 허수 수치가 임계치를 넘기는 순간 오픈클로가 compaction을 돌리려 하고, 그러다 없는 하네스를 부르면서 터지는 흐름이었던 거다.

정리하면 에러 메시지도, 그게 가리키던 토큰 폭발도 둘 다 진짜가 아니었던 셈이다. "등록이 안 됐다"는 것도 사실이 아니고, 그걸 유발한 "토큰 폭발"도 허수였고.

일단 푸는 법 (워크어라운드)

원인을 알고 나니 응급처치는 단순했다. 막힌 세션을 새 세션으로 리셋해버리면 된다. 컨텍스트가 0으로 돌아가니 임계치를 안 넘고, 그러면 compaction도 안 돌고, 에러도 안 난다.

채팅이 살아 있으면 그 방에서 /new 한 줄이면 된다. 근데 이미 완전히 막혀서 /new조차 안 먹으면, 게이트웨이를 잠깐 멈추고 세션 기록에서 그 세션만 직접 지워주면 된다.

bash
openclaw gateway stop
cp ~/.openclaw/agents/main/sessions/sessions.json ~/sessions.json.bak   # 백업 먼저
# 막힌 세션 키만 삭제 (키는 본인 것으로)
jq 'del(.["agent:main:telegram:..."])' ~/.openclaw/agents/main/sessions/sessions.json > /tmp/s.json \
  && mv /tmp/s.json ~/.openclaw/agents/main/sessions/sessions.json
openclaw gateway start

세션 엔트리만 지우는 거라 대화 기록(.jsonl)은 디스크에 그대로 남고, 어차피 긴 맥락은 Claude Code가 들고 있으니 "대화 능력"을 잃는 것도 아니다. (/new랑 효과는 똑같고, 채널이 완전히 먹통이 됐을 때 쓰는 오프라인 버전이라고 보면 된다.)

알고 보니 이미 고쳐져 있었다

사실 원인을 파악하고 난 내 첫 충동은 "그냥 내가 고쳐버리자"였다. 고치는 코드는 별것 아닐 것 같았으니까. 소스에서 정확히 어느 함수가 claude-cli를 harness 선택에 넘기고 있는지까지 짚어봤고(selection.tsselectAgentHarnessDecision 근처가 범인이었다), 거기 가드 하나 넣어서 PR을 날려버릴 생각이었다.

근데 그 전에 문득 이런 생각이 들었다. 이거 진짜 나만 겪는 건가? claude-cli 런타임에 Claude 구독으로 오픈클로 돌리는 사람이 한둘이 아닐 텐데, 나만 이럴 리가 없잖아. 그래서 PR을 쓰기 전에 깃헙 이슈부터 뒤져봤다.

아니나 다를까, 이미 똑같은 증상이 보고돼 있었다. 그것도 아주 따끈따끈하게.

  • #84857 컴팩션 preflight가 contextThreshold를 넘은 claude-cli 세션에서 MissingAgentHarnessError를 던진다는 내용이었다. 내 증상 그대로에, 트리거 조건이랑 재현 절차까지 똑같이 적혀 있었다.
  • #85626 같은 에러의 형제 격인, 세션 재개(resume) 쪽 버그. 이건 거의 그날 올라온 거였다.
내 증상과 똑같이 적혀 있던 깃헙 이슈 #84857
내 증상과 똑같이 적혀 있던 깃헙 이슈 #84857

"아, 나만 겪는 게 아니었구나" 하는 묘한 안도감이 들었다. 생각해보면 비공식적인 방법이긴 해도 Claude CLI 구독으로 등록해서 싸게 쓰려는 사람이 꽤나 많을 텐데 이 증상이 나만 겪는게 말이 안되긴 했다. 그런데도 이게 이제야 따끈따끈하게 보고가 되고 있었다는 게 좀 의문이긴 하지만 솔직히 그 원인까진 알아보지 않았다.

더 놀라운 건 그다음이었다. 이슈에 연결된 고치는 PR이 이미 올라와 있었다.

  • #84878 #84857을 고치는 PR이었다. 내가 짜려던 거랑 똑같이, CLI 런타임이면 harness 선택을 건너뛰게 하는 가드를 넣는 내용이었다.
  • #85627 #85626을 고치는 PR.

심지어 PR들을 들여다보고 있는데, 내가 보고 있는 그 사이에 봇(ClawSweeper)이 #84878을 자동 머지하는 PR(#85862)을 만들어서 불과 한두 시간 전에 main에 머지해버린 게 보였다. 머지 시각이 거의 실시간이라 "어, 방금 들어갔네?" 싶은 타이밍이었다. 내가 PR 데뷔 한번 해보겠다고 마음먹은 그 시점에, 이미 누군가 다 해놓고 봇이 도장까지 찍어주고 있던 셈이다;;

내가 보고 있던 사이에 main에 머지돼 버린 PR #85862
내가 보고 있던 사이에 main에 머지돼 버린 PR #85862

김이 좀 빠지긴 했지만, 어쨌든 정말 고쳐졌는지는 확인해야 했다. 그래서 소스를 직접 받아서 버전별로 그 가드(cli_runtime_passthrough_pi)가 들어갔는지 대조해봤다. 내가 쓰던 stable(2026.5.22)엔 그 가드가 아예 없어서 claude-cli면 그냥 throw였고, 그 다음 alpha/beta랑 main엔 가드가 들어가 있어서 claude-cli면 throw 대신 PI로 비켜가게 돼 있었다. 즉 버그는 이미 main에 고쳐져 있었던 거다. 내가 쓰던 stable에 그 수정이 아직 안 들어가 있었던 것뿐이고, 내가 할 일은 코드를 고치는 게 아니라 고친 버전이 올라오기를 기다리는 거였다.

그래서 지금은

처음엔 고친 코드가 깃헙 main에만 있고 npm에는 아직 안 올라와 있는 상태였다. 그래서 당장은 두 가지로 버텼다.

  • 세션이 막히면 /new로 그때그때 리셋하고,
  • npm에 2026.5.22보다 높은(베타 포함) 버전이 올라오는지 매일 자동으로 확인하게 해뒀다. (오픈클로한테 스케줄 작업을 하나 걸어둔 거다.)

그런데 이게 바로 다음 날 진짜로 걸렸다. 자동 점검이 새로 publish된 베타를 잡아냈고, 공식 문서대로 채널만 베타로 바꿔서 업데이트했다.

bash
openclaw update --channel beta

(openclaw update는 설치 방식을 알아서 감지해서 받고, openclaw doctor까지 돌린 다음 게이트웨이를 재시작해준다.) 이렇게 v2026.5.24-beta.2로 올라갔고, 그 뒤로는 이 에러가 깔끔하게 사라졌다. 대화를 아무리 길게 이어가도 더 이상 harness가 어쩌고 하는 에러가 안 뜬다. 지금은 그냥 잘 쓰고 있다.

결국 24/7 홈서버를 소스 빌드로 갈아엎지 않고, 막히면 /new로 버티다가 정식 수정이 베타에 올라온 타이밍에 명령어 한 줄로 끝낸 셈이다.

남는 것

사실 저 agent harness "claude-cli" is not registered 에러를 처음 봤을 때 그대로 구글링 해봐도 아무것도 안 나왔다. 그래서 방향을 틀어 오픈클로 오픈소스 저장소의 이슈를 직접 뒤졌고, 거기서 같은 증상을 만난 거였다.

그래서 이번에 남은 건 거창한 교훈이라기보단 이 과정 자체였다. 막연하던 증상을 끝까지 좁혀서 정확히 뭐가 문제인지 알게 됐고, 오픈소스에서 같은 버그의 이슈와 픽스 PR이 거의 실시간으로 올라오고 머지되는 현장을 목격했고, 그러다 '내가 PR 올릴 수도 있었겠는데' 하는 데까지 갔다(한 끗 차이로 누가 먼저 올려놨다). 다음엔 좀 더 빨리 움직여보자는 생각이 들었다.

사실 이 글은 한창 막혀서 끙끙대던 시점에, 결론도 안 난 채로 "엉망으로라도 기록은 남겨두자"는 마음으로 쓰기 시작한 거였다. 그런데 쓰는 사이에 진짜로 해결이 돼버려서, 결국 깔끔한 결말까지 붙어버렸다. 시작을 미뤘으면 이 타이밍도 없었을 거다. 그래서 더더욱, 나중에 같은 에러로 검색해서 여기까지 흘러온 누군가한테 한마디면 충분하다. 그 에러 메시지가 진짜 원인이 아니니까, 세션이 길어졌으면 일단 /new 하고, 2026.5.24-beta.2 이상으로 올리면 된다.