Issue 01중국 AI
AC POST
중국 AI 목록
掘金2026년 11월 1일 11:07중국어 → 한국어

Claude Code 잦은 Compacting, v0.5.1에서 두 가지 문제 해결

v0.5.0 공개 다음 날 나온 v0.5.1에서 1M 창 15% 지점의 잦은 Compacting을 context_window를 모델 선언에 넣어 해결했고, ccm run의 --resum 전달 문제도 다뤘다.

중국어 원문을 AI로 번역했습니다. 고유명사와 수치는 원문 표기를 우선하며, 중요한 판단에는 아래 출처 원문을 함께 확인하세요.

v0.5.0이 나온 다음 날 바로 v0.5.1이 나왔다——사고 수습이 아니라 내가 가장 좋아하는 부류의 반복이다. 아픈 부분을 근본부터 고치고, 하루 만에 마무리하고, 게이트 하나도 생략하지 않았다——두 개의痛点(통점), 각각 한 번씩 근본 치료.

앞 글을 읽지 않은 독자에게 한 줄 배경을 주자면: CCM(Claude Code Model Manager)은 내가 Rust로 작성한 단일 바이너리 「로컬 모델 라우팅 제어 평면」이다——Claude Code를 임의의 서드파티 모델 게이트웨이(z.ai, DeepSeek 등)로 가리키게 하고, 중간에 라우팅, 서킷 브레이커, 관측, 런타임 전환을 장착한다. 프로젝트를 시작한 이유는 75번째 글에서 다뤘으니 여기서 펼치지 않는다.

먼저 타임라인을 나열한다. 모두 리포지토리의 docs/V0.5_PLAN.md 기록에서 나온 것이다:

날짜 이벤트 10-10 v0.5.0 tag + Release 10-10 greenlight「ccm 측 근본 치료」, 반영 commit 2d39dae——발단: 직결 glm 장세션(긴 세션)이 1M 윈도우의 ~15% 지점에서 조기 압축됨 10-11 --resume 지원 여부를 한 번 물어봄 → greenlight「구현해라, 그리고 문서 수정에 주의」(commit 84e4d2d) 10-11 v0.5.1 릴리스: tag 전 docs 감사에서 7개 결함이 전부 문서(수정해 tag commit 05ab15c에 포함), CI 듀얼 러너 녹색, 산출물 + LF checksums 재다운로드 검증

痛点 하나: 1M 윈도우인데, 15%에서부터 Compacting

증상과 기제

나는 ccm run glm으로 직결 장세션을 붙였다(glm-5.3은 z.ai 경유, 실제 컨텍스트 윈도우 1M, z.ai 문서 기준). Claude Code 인터페이스가 Compacting conversation을 자주 튀어나오기 시작했다. 압축 지점이 어디인가? V0.5_PLAN §11의 반영 기록이 아주 적나라하게 적고 있다: 세션이 실제 1M 윈도우의 ~15% 지점에서 자동 압축된다(단일 사례 실측, 약).

150K 근처에서 곧바로 압축을 시작하는 것, 역산하면 Claude Code가 glm-5.3을 200K 등급의 낯선 모델로 취급하고 있다는 뜻이다. 「가정한 작은 윈도우에 따라 조기 압축」하는 동작은 문서에 기록된 바 있다.

200K라는 숫자도 허공에서 추론한 것이 아니다——커뮤니티 기준 약 200K이고, 공식 문서는 pinned 모델 시나리오에 대해 200K 기본값을 명문으로 기록하고 있다(리포지토리 USAGE 5.3 기록). 다만 공식이 unpinned 서드파티 id를 커버하지 않는데, 그 등급은 경험값이다【업계 종사자 판단】.

기제를 한 문장으로: Claude Code는 서드파티 모델 id를 인식하지 못하고, 해석하지 못하면 보수적으로 가정한 작은 윈도우에 따라 proactive auto-compaction을 한다(README / V0.5_PLAN §11 기준). 그것은 고장 난 게 아니라 당신을 보호하는 것이다——다만 보호의 파라미터가 엉뚱한 대상에 맞춰져 있을 뿐이다.

열쇠는 사실 이미 존재했다: CLAUDE_CODE_MAX_CONTEXT_TOKENS. §11에 기록된 적용 조건——해석 불가능한 id에는 곧바로 적용된다(claude code v2.1.193+). 그것이 인식하는 claude-* id에는 무효이며, DISABLE_COMPACT를 겹쳐야 한다. 공식 문서를 기준으로 한다.

192개 테스트가 전부 녹색이던 v0.5.0은 이에 대해 아무것도 몰랐다. 테스트를 탓할 일은 아니다: 기제가 남의 클라이언트 동작 속에 숨어 있어, 리포지토리 안에는 그것을 표현할 수 있는 테스트가 하나도 없다——v0.5에서 count_tokens를 할 때 이미 이 경계를 확인했다(the repo cannot test client behavior, V0.5_PLAN §3.3). 릴리스 전에 spike로 한 번 비춘 적이 있고(17회 재시도 폭풍, 81번째 글 참조), 이번에는 실제 사용 차례였다.

왜 패치를 때리지 않는가

가장 손쉬운 패치는 누구나 한다: README에 절 하나를 추가해서 사용자에게 매번 시작 전에 수동으로 CLAUDE_CODE_MAX_CONTEXT_TOKENS=1000000을 export하라고 가르치는 것.

왜 안 하는가? 이 방식은 「모델의 속성」을 「shell의 속성」으로 잘못 놓아버리기 때문이다. 터미널을 바꾸든, 기계를 바꾸든, 모델을 바꾸든, 이 env는 어긋난다——윈도우는 분명히 모델에 붙어 있는데.

가장 아이러니한 것은 ccm 자신조차 이 실수를 허용하지 않는다는 점이다: 프록시 모드는 지금까지 어떤 윈도우도 주입하는 것을 거부하며, 그 이유는 §11에 기록돼 있다——런타임 ccm switch가 시작 시점에 주입된 윈도우를 만료시키므로, launch-time의 윈도우는 runtime의 모델에 맞지 않는다. 내가 어찌 스스로도 범하기 거부하는 실수를 사용자 매뉴얼로 포장해 가르칠 수 있겠는가?

그래서 근본 치료의 형태는 자연스럽다: 윈도우가 모델 선언의 일급 필드가 되고, 직결 시작 시 ccm이 주입한다. 당시의 결정, 계획에 기록된 greenlight 원문은 한 문장뿐이었다: 「ccm 측 근본 치료」.

반영: 하나의 필드, 그 모든 경계와 함께

[models.glm] provider = "zai" model_id = "glm-5.3" context_window = 1000000

동등한 CLI 표기 ccm add model glm --provider zai --model-id glm-5.3 --context-window 1000000

값의 규율: >= 1, 0은 parse/load/add 세 계층에서 거부된다; CLI 상한은 i64::MAX(TOML 정수 천장)에 못 박혔다——parse는 되지만 저장할 수 없는 값은 flag 지점에서 바로 오류를 내고, config save까지 가서 터지게 두지 않는다.

본줄 한 줄, 경계 여섯 개——경계가 빠진 수정은 다른 모드에서 재발한다. 아래 출처는 모두 V0.5_PLAN §11과 README다:

- 직결 시작 시 CLAUDE_CODE_MAX_CONTEXT_TOKENS 주입; 프록시는 절대 주입하지 않음(이유는 위와 같음)

- starter 설정 glm = 1M(z.ai 문서 기준의 능력 사실); claude-*는 선언하지 않음, Claude Code가 그것들의 윈도우를 원래 인식하므로

- ccm discover 등록에는 윈도우를 붙이지 않음——게이트웨이의 /v1/models에는 이 데이터가 없으므로, pricing과 마찬가지로 「수기 사실」 방침을 따르며 결코 추측하지 않음

- ccm doctor 3상태 진단: 선언됨 → ✓; 선언되지 않은 서드파티 current → ! 힌트; openai-compatible(프록시 전용, 주입 약속 미적용) → 행 자체가 나타나지 않음

- doctor는 또한 Claude Code 자체 settings 안에 충돌하는 동명 env가 있는지도 검출한다——단, 「이미 선언되었고 직접 연결 가능」할 때만 조회하며, 선을 넘어 경고하지는 않는다

- /_ccm/models는 선언된 경우에만 필드를 싣고, key를 선언하지 않으면 그냥 산출되지 않는다(wire shape에 테스트로 고정됨)

대항 검증(adversarial validation)은 세 라운드를 거쳤다. 첫 라운드는 도중에 중단됐다——검증 agent가 review-only 지시를 무시하고 워킹 트리를 수정했고, 두 곳의 미승인 편집을 롤백한 뒤 전체 diff를 놓고 심사한 끝에야 통과시켰다(전말 교훈은 제82편 《让 Agent 审 Agent》에 있다). 이후 전부 읽기 전용 Explore agent + git-diff 변조 봉인으로 고정했다.

간신히 건져낸 findings로 다섯 개의 경계를 단단히 했고, 두 번째 라운드(봉인 완전, 이 기능만 집중 심사)에서는 12 confirmed / 3 plausible / 5 refuted가 나왔고, 다시 여섯 개의 경계를 단단히 했다——가장 단단한 한 가지: doctor의 창 진단은 provider가 빠진 모델까지 경고하지만, 그런 기동은 주입이 일어나기 전에 중단되므로 진단은 필연적으로 허탕을 친다. 이제 provider 조회가 창 검사보다 앞서 배치되어, 이 관문은 테스트로 고정된 순수 술어(predicate)로 수렴했다.

세 번째 라운드는 출시 전 docs 감사(7건, 타임라인 참조)다.

두 번째 고통점: --resume을 넘길 수 없다

한 가지 문제에서 한 줄의 결정까지

10월 11일, 나는 ccm run을 보고 소박한 질문을 던졌다: claude --resume <session-id>가 가능한가? 불가능했다——ccm은 claude 자식 프로세스를 감싸서 기동하는데, 인자 통로를 아예 남겨두지 않았다.

V0.5_PLAN §11에 기록된 greenlight 원문: 「구현하자, 그리고 문서 수정에 유의하라」.

프로세스를 한 겹 감싸는 숨은 비용: 호스트에 인자가 하나 늘 때마다, 래핑 계층은 관문을 하나 빚진다.

설계: -- 이후의 세계를 그대로 넘겨준다

ccm run [flags] [target] -- <claude args...> # 예를 들어 세션을 이어간다(<session-id>를 당신의 세션 id로 바꿔라): ccm run glm -- --resume <session-id>

구현 핵심은 clap의 #[arg(last = true)]이다: -- 이후의 모든 것이 그대로 포획되어 claude 뒤에 추가되며, 직결과 프록시 두 모드 모두에서 유효하다.

세 가지 세부 사항. 첫째, -- 이후의 내용은 절대 target 위치 인자를 채우지 않는다——clap은 --를 보는 순간 마지막 positional로 건너뛰므로, ccm run -- --resume <id>는 지속화된 기본 target을 기동하며 --resume을 모델명으로 잘못 삼키지 않는다.

둘째, 내가 가장 신경 쓰는 회귀 못 하나: ccm run glm --proxy는 반드시 여전히 프록시 모드여야 한다. 꼬리부 포획을 한 번 탐욕적으로 쓰면 이 명령은 소리 없이 직결 기동으로 뒤집힌다——테스트가 이 동작을 전용으로 고정해, 미래의 리팩터링이 그것을 슬며시 삼키지 못하게 한다.

셋째, clippy가 프록시 세션 인자를 강제로 하나의 Option<(&str, &str)>로 모으도록 한다——두 값은 둘 다 있거나, 둘 다 없어야 한다. 원래는 두 Option 사이의 신사협정이었는데, 이제는 컴파일 타임의 사실이 되었다.

Windows 가드레일: 조용히 추측하느니, 크게 거부한다

Windows에서 ccm은 cmd /c를 통해 claude를 기동하는데, & | < > ^ " 와 줄바꿈 문자가 바로 노출된 주입면이다(다른 플랫폼은 셸 없이 직접 exec하므로 이 문제가 없다).

정책은 이스케이프가 아니라 거부다: 메타문자를 포함한 투과 인자는 어떤 부작용도 일으키기 전에 이름을 짚어 오류를 내고, exit 1한다. 계획에 기록된 정책 원문: "loud rejection beats guessing an escaping strategy"—— 거부는 크게, 이스케이프 전략을 추측하느니 낫다.

두 가지 세부 사항은 모두 대항 검증이 짜낸 것이다:

- 「어떤 부작용도 일으키기 전에」를 진짜로 지킨다: 검증을 proxy pre-switch 앞으로 끌어올렸다——pre-switch는 clients.toml을 지속적으로 기록하므로, 먼저 전환하고 나서 오류를 내면 더러운 상태가 남는다

- 메타문자 집합에 처음에는 줄바꿈 문자가 빠져 있었다: 날 줄바꿈은 cmd에서 곧 명령 구분자이며, 표준 라이브러리가 대신 인용부호를 붙여주지 않는다

%PAIR% 변수 확장은 문서에 기록된 caveat이며, 막지 않는다——막으려면 cmd의 확장 의미를 추측해야 하고, 그건 다시 이스케이프를 추측하는 옛 길로 돌아가는 것이다.

검증이 잡아낸 것

두 번째 고통점에도 별도의 대항 검증 라운드를 돌렸다(전부 읽기 전용 agent + 봉인 완전) 15 findings, 중복 제거 후 7 confirmed, 나머지 6+2건은 근거를 갖춰 기각됐다. 가장 값진 한 가지: launcher의 argv 테스트가 Linux CI에서 panic한다——get_args()는 프로그램명을 포함하지 않는데, Windows에서는 우연히 무해했다.

수정법은 단언을 느슨하게 하는 것이 아니라, WSL2에서 실제로 돌려 두 플랫폼 각각의 전체 argv를 테스트에 고정하는 것이다.

또 다시 「실제 환경 × 경계 접합부」다: 게이트가 전부 초록이어도 막지 못하는 것은 모두 두 강판의 용접 이음새 속에 숨어 있다.

E2E 스모크(Windows, V0.5_PLAN §11에 기록된 세 가지 결말):

-- --version 투과 → claude 버전을 출력하고 exit 0, cmd 메타문자를 포함한 인자 → 이름을 짚어 거부하고 exit 1, claude 인자를 -- 앞에 깜빡 둔 경우 → clap 구분자 힌트를 주고 exit 2

댓글창 예상 답변: 반드시 올 세 가지 반론

반론 하나: 말을 바꾸는 건가? v0.5 계획에는 context window를 Model schema에 지속화하지 않는다고 분명히 적혀 있다——이 non-goal은 확실히 존재하며, V0.5_PLAN §4의 판단 기준 원문은: "point-in-time facts go stale in config"——시점 사실을 설정에 적으면 만료된다. 하지만 그것은 probe가 자동 탐지한 데이터를 겨냥한 것이다; 이번에 실제로 적용한 것은 손으로 입력한 선언이며, 사용자가 자신이 고른 모델에 책임을 지며, pricing 표와 같은 정책이다.

계획 §4는 동시에 뒤집힘 그 자체도 기록해두었다: "reversed post-release as a hand-entered declaration". 뒤집을 수 있다, 뒤집으면 반드시 흔적을 남겨야 한다——이것이 「문서가 곧 계약」의 나머지 절반이다.

반론 둘: 하루에 한 버전, 혹시 v0.5.0은 제대로 테스트도 안 하고 내보낸 것 아닌가?

정반대로, v0.5.1은 관문 하나도 줄이지 않았다——타임라인의 마지막 줄이 바로 체크리스트다: docs 감사, 듀얼 플랫폼 게이트, CI 양쪽 runner 그린(run 38105354005), checksums, 재다운로드 검증(V0.5_PLAN §2). 두 차례 배포 감사(v0.5.0 confirmed 8건, 중복 제거 5개 결함; v0.5.1 7건)의 결함은 전부 문서였다——코드는 테스트가 받쳐 주지만, 문서는 감사에만 의지할 수 있다.

「제대로 테스트하지 못했다」는 점에 관해서라면—— 이런 경계 지점의 문제는 실제 사용만이 비춰 줄 수 있다. 배포는 테스트의 종결 판결이 아니라, 탐지의 출발점이다. 한 달 모아서 메이저 버전을 낸다고? 그동안 매번의 Compacting이 통증이 아직 살아 있음을 일깨워 줄 것이다.

반박 3: 환경 변수 한 줄이면 해결되는데, 왜 이렇게 무겁게?

한 번의 기동은 해결할 수 있어도 어긋남(misalignment)은 해결하지 못한다——창(window)은 셸의 속성이 아니라 모델의 속성이기 때문이다. 완전한 논증은 위의 「왜 패치하지 않는가」 절을 참조하라.

Claude Code 자체의 settings.json에 써넣는다고? 그 env는 정적이라, ccm switch가 런타임에 모델을 바꾸면 조용히 만료된다—— 이것이 바로 프록시 모드가 launch-time 주입을 거부하는 것과 동일한 이유다. 주입은 「현재 직결된 그 모델」을 따라가야 하므로, 그 집은 모델 선언 안에 있다; doctor는 settings 안의 동명 충돌을 검출하는데, 겨냥하는 것이 바로 이 정적 env와 선언이 충돌하는 순간이다.

방법론: 아프면 근치하는 세 요소

두 번의 반복은 하나의 골격을 공유하며, 뜯어 보면 세 가지다:

- 메커니즘을 정위하라, 증상에 머물지 마라. 빈번한 Compacting은 증상이다; 「클라이언트가 가정된 작은 창으로 낯선 id를 관리하는 것」이야말로 메커니즘이다. 증상에 대고 고치는 것(사용자에게 기대치를 낮추라고 가르치고, 참으라고 하는 것)은 위약이다.

- 올바른 계층에서 고쳐라. 창은 모델의 속성 → 모델 선언으로 들어간다; 사용자 매뉴얼에 들어가지 않고, 셸 env에 들어가지 않고, FAQ에 들어가지 않는다. --resume도 마찬가지다: 올바른 집은 그대로 통과시키는 통로이지, 갈수록 길어지는 파라미터 대조표가 아니다. 판단 구호: 이 지식은 어떤 데이터 구조에 살아야 하는가?

- 경계와 본줄기를 함께 떨어뜨려라. 프록시 비주입, doctor 삼态, discover 추측 없음, claude-* 선언 면제——happy path만 고치는 근치는 반쪽 근치이며, 나머지 반쪽은 다른 모드에서 재발한다.

패치와 근치의 차이는 표 하나로 정리된다:

패치 근치 지식이 사는 곳 사용자 매뉴얼 / FAQ [models.<name>] 일급 필드 오류를 누가 발견하나 사용자가 issue를 검색 ccm doctor 기동 즉시 진단 모델 교체 / 터미널 교체 env 어긋남 선언이 모델을 따라감 다음 사용자 함정을 다시 밟음 무감

ccm의 가치는 전부 「내 설정 × 남의 클라이언트」라는 이 이음매 위에 있다——이음매 위의 동작은 근치하든가, 아니면 경계를 선언으로 써 두든가 해야 하며, 유독 얼버무려서는 안 된다.

지금 당장 할 수 있는 일

Claude Code로 서드파티 게이트웨이에 연결하는 독자라면, 30초 자가 점검:

ccm을 설치한 경우: 먼저 검진을 돌려 창 진단 줄을 본다 ccm doctor # ✓ 선언됨 / ! 서드파티 모델 미선언 / 줄 전체가 나타나지 않음(openai 호환) # 이어서 통과 통로를 검증한다 ccm run glm -- --version # claude 버전을 출력하고 exit 0이면, 통로는 뚫린 것이다

ccm을 쓰지 않는 경우, 한 가지만 기억하면 충분하다: 게이트웨이가 분명 큰 창인데 Compacting conversation이 빈번하다면, 먼저 Claude Code가 당신의 모델 id를 아는지 확인하라—— CLAUDE_CODE_MAX_CONTEXT_TOKENS는 낯선 id에 대해 곧바로 효력을 발휘한다.

두 기능의 완전한 구현 기록은 저장소 docs/V0.5_PLAN.md §11에 있다; ccm 본체는 github.com/Thneoly/ccm(MIT, 206 tests, Windows + Linux 듀얼 플랫폼 검증)에 있다. 《CCM 조조기》 제83편, 다음 편에서 보자.