Claude Code 실전 가이드 2편입니다. 1편에서는 시작하기와 CLAUDE.md 를 다뤘고, 이번 편은 컨텍스트 창을 다루는 방법입니다. 컨텍스트가 왜 유한한 자원인지, 무엇이 그걸 갉아먹는지, 그리고 어떻게 지켜서 세션이 오래 멀쩡하게 굴러가게 만드는지를 실제로 쓰면서 정리한 습관 위주로 적습니다.
컨텍스트는 돈이자 집중력이다
Claude Code 를 쓰다 보면 처음엔 "컨텍스트 창이 크니까 뭐든 다 넣어도 되겠지"라는 생각이 들기 쉽습니다. 실제로는 그렇지 않습니다. 컨텍스트 창은 두 가지 의미에서 유한한 자원입니다.
첫째, 물리적으로 한계가 있습니다. 아무리 크더라도 대화가 길어지면 언젠가 꽉 찹니다. 둘째, 더 중요한 문제인데 컨텍스트가 커질수록 모델이 지금 진짜 중요한 정보를 찾아내는 정확도가 떨어집니다. 파일 덤프 수백 줄 사이에 묻힌 핵심 한 줄보다, 필요한 부분만 깔끔하게 들어와 있는 편이 결과물 품질이 확실히 좋습니다. 컨텍스트 관리는 용량 문제이기 전에 신호 대 잡음비 문제입니다.
그래서 저는 세션을 운영할 때 "이 정보를 지금 컨텍스트에 넣을 가치가 있는가"를 계속 따지는 편입니다. 그냥 다 보여주고 알아서 걸러내라는 태도는 결과물을 나쁘게 만듭니다.
무엇이 컨텍스트를 채우는가
실제로 세션을 운영해보면 컨텍스트를 잡아먹는 주범은 대개 이 세 가지입니다.
파일 전체 덤프. 수백 줄짜리 설정 파일이나 로그 파일을 통째로 읽어서 대화에 붙여 넣으면, 그 안에서 실제로 필요한 건 한두 줄뿐인 경우가 대부분입니다. 나머지는 전부 컨텍스트를 채우는 잡음입니다.
긴 로그. CI 파이프라인 로그, 서버 에러 로그, curl -v 출력 같은 건 몇 줄만 필요해도 전체가 통으로 들어오기 쉽습니다. 특히 에러 스택트레이스는 반복되는 프레임워크 내부 호출이 절반을 넘게 차지하는 경우가 많아서, 실제 원인이 되는 한두 줄과 나머지 잡음의 비율이 안 좋습니다.
실패한 시도. 코드를 고치다가 잘못된 방향으로 몇 번 삽질하고 나면, 그 실패한 시도들의 대화 이력이 전부 컨텍스트에 남습니다. 문제는 이게 단순히 자리만 차지하는 게 아니라, 모델이 다음 시도를 판단할 때 이전의 잘못된 맥락에 끌려가는 앵커링(닻 내림) 효과가 생긴다는 점입니다. 삽질이 길어질수록 다음 접근도 같은 함정에 갇히기 쉬워집니다.
/clear 와 /compact — 언제 쓰는가
컨텍스트를 정리할 때 실제로 제일 자주 쓰는 명령은 두 개입니다. /clear 는 대화를 완전히 비우고 새로 시작하는 것이고, /compact 는 지금까지의 대화를 요약해서 압축하는 것입니다.
이 둘을 언제 쓰느냐가 실전에서는 생각보다 헷갈립니다. 저는 기준을 단순하게 잡았습니다. 작업 단위가 완전히 끝났을 때는 /clear, 같은 작업을 계속 이어가야 하는데 컨텍스트만 무거워졌을 때는 /compact.
제 워크플로에서 "작업 단위"는 GitLab 이슈 하나입니다. 이슈를 등록하고, 브랜치를 파고, 구현하고, 검증하고, 머지까지 끝나면 그 이슈는 완결된 겁니다. 이 시점에서 다음 이슈로 넘어가면서 지난 이슈의 삽질 이력, 파일 덤프, 디버깅 로그를 그대로 들고 갈 이유가 없습니다. 오히려 들고 가면 다음 이슈와 무관한 맥락이 신호 대 잡음비를 계속 깎아먹습니다. 그래서 이슈 하나가 끝나면 무조건 /clear 하고 새 세션처럼 다음 이슈를 시작합니다.
반대로 /compact 는 아직 같은 이슈, 같은 작업 도중에 쓰는 명령입니다. 예를 들어 원인 파악에 시간이 오래 걸려서 대화가 길어졌는데, 지금까지 알아낸 사실(어디가 문제였는지, 무엇을 확인했는지)은 계속 필요한 상황이라면 /compact 로 압축해서 핵심만 남기고 이어갑니다. 작업을 끝내는 게 아니라 같은 작업을 더 가볍게 이어가는 것이 목적입니다.
이 구분을 안 하고 아무 때나 /compact 만 계속 누르면, 서로 무관한 여러 작업의 요약이 계속 쌓여서 오히려 맥락이 뒤섞입니다. 반대로 이슈가 끝났는데도 /clear 를 안 하고 계속 이어가면, 다음 이슈에 지난 이슈의 코드 경로나 변수명이 섞여 들어오는 경우가 생깁니다. "한 세션 = 한 이슈" 규율은 결국 이 문제를 원천적으로 막기 위한 겁니다.
서브에이전트에게 탐색을 위임한다
컨텍스트를 지키는 방법 중 실전에서 가장 효과가 큰 건 탐색 작업 자체를 메인 세션 밖으로 빼는 것입니다. Claude Code 는 서브에이전트(subagent)를 띄워서 별도의 컨텍스트 창에서 작업을 시키고, 그 결과만 메인 세션으로 돌려받을 수 있습니다.
예를 들어 "이 함수가 레포 전체에서 어디서 호출되는지 다 찾아줘" 같은 작업이 있습니다. 메인 세션에서 직접 하면 grep 결과, 열어본 파일들의 내용, 호출부 주변 코드까지 전부 메인 컨텍스트에 쌓입니다. 정작 필요한 건 "어디서 호출되는지"라는 결론 하나인데, 거기 도달하는 과정의 부산물을 메인 세션이 다 떠안게 되는 겁니다. 이걸 서브에이전트에게 맡기면 파일을 몇 개 읽고 grep 을 몇 번 돌리든 그 과정은 서브에이전트 쪽 컨텍스트에서만 소비되고, 메인 세션에는 정리된 결론만 들어옵니다.
여기서 짚어둘 건, 이 패턴의 값어치가 병렬성보다 컨텍스트 보호 쪽에 있다는 점입니다. 서브에이전트를 여러 개 동시에 띄워 시간을 줄이는 이야기는 3편에서 따로 다루는데, 속도를 전혀 신경 쓰지 않고 하나만 띄우더라도 "메인 세션을 과정이 아니라 결과로만 채운다"는 효과 하나만으로 이미 쓸 이유가 됩니다.
파일 전체 읽기 대신 부분적으로 본다
컨텍스트를 아끼는 가장 기본적인 습관은 파일을 통째로 읽지 않는 것입니다. 필요한 게 특정 함수나 특정 설정값이라면 grep 으로 위치를 먼저 찾고, 그 주변만 읽는 편이 훨씬 낫습니다. 수천 줄짜리 파일 하나를 통으로 읽으면 그 파일 안에서 실제로 이번 작업과 관련 있는 부분은 대개 일부뿐이고, 나머지는 컨텍스트만 채우는 잡음이 됩니다.
로그도 마찬가지입니다. CI 파이프라인이 실패했을 때 로그 전체를 붙여넣기보다, 에러가 난 지점 근처만 잘라서 보는 편이 낫습니다. 반복되는 프레임워크 스택트레이스나 정상 동작 중인 이전 단계 로그까지 다 들여다볼 필요는 없습니다.
큰 출력은 파일로 빼고 경로만 넘긴다
작업을 하다 보면 결과물 자체가 큰 경우가 있습니다. 데이터베이스 조회 결과가 수백 행이라거나, 스크립트 실행 결과가 길다거나 하는 경우입니다. 이런 걸 전부 대화 안에 그대로 출력시키면 그 순간 컨텍스트가 크게 불어납니다. 제 경우 여러 서비스가 PostgreSQL 단일 클러스터를 공유하는 구조라 "어느 서비스가 커넥션을 얼마나 쓰고 있나" 같은 조회를 자주 하는데, 이런 질의는 결과 행이 금방 수백 개가 됩니다. 그 전체가 대화에 남아봐야 다음 판단에 쓰이는 건 상위 몇 줄뿐입니다.
이럴 때는 결과를 파일로 저장하고, 대화에는 그 파일 경로와 요약만 남기는 편이 낫습니다. 필요하면 나중에 그 파일을 다시 열어서 특정 부분만 확인하면 되고, 전체 내용이 계속 대화 컨텍스트에 남아있을 필요는 없습니다. 저장한 스크립트나 조회 결과를 프로젝트의 scripts/ 같은 곳에 흔적을 남기는 것도 같은 맥락입니다. 컨텍스트에는 안 남기되, 나중에 다시 봐야 할 수도 있는 건 파일로 남겨서 필요할 때만 다시 불러오는 방식입니다.
영속 메모리와 세션 컨텍스트는 다른 것이다
1편에서 다룬 CLAUDE.md 같은 파일 기반 메모리는 세션 컨텍스트와 성격이 다릅니다. 세션 컨텍스트는 이번 대화가 끝나면(또는 /clear 하면) 사라지는 휘발성 정보고, 영속 메모리는 세션이 바뀌어도 계속 남아서 매번 다시 로드되는 정보입니다. /clear 를 자주 하는 습관이 성립하려면 이 영속 메모리 쪽이 제대로 채워져 있어야 합니다. 비우고 시작해도 매번 다시 설명할 필요가 없는 건, 다시 설명해야 할 내용이 이미 파일에 적혀 있기 때문입니다.
이 차이 때문에 두 곳에 넣을 정보의 성격이 달라야 합니다. "이번 작업에서 지금 이 파일을 이렇게 고치고 있다" 같은 건 세션 컨텍스트에만 있으면 충분합니다. 반면 "이 프로젝트는 어떤 브랜치 전략을 쓴다", "이 인프라의 이 포트는 이런 용도다" 같은 건 세션이 바뀌어도 계속 알아야 하니 영속 메모리에 적어둡니다.
문제는 영속 메모리가 낡는다(decay)는 점입니다. 어느 시점엔가 적어둔 "이 서비스는 이 포트를 쓴다"거나 "이 정책의 임계값은 얼마다" 같은 기록이, 실제로는 그 뒤에 인프라가 바뀌거나 정책이 조정되면서 더 이상 맞지 않는 경우가 생깁니다. 메모리는 적을 당시엔 사실이었지만 시간이 지나면 사실이 아닐 수 있습니다. 그런데 메모리 파일 자체는 "언제 적었다"는 정보만 있을 뿐 "지금도 맞다"는 보장은 안 해줍니다.
그래서 날짜나 특정 값이 박힌 메모리를 근거로 실제 행동(코드 수정, 배포 등)을 하기 전에는, 그 핵심 단언을 실제 코드나 데이터베이스, 이슈 상태로 한 번 확인하는 습관이 필요합니다. 예를 들어 메모리에 "이 이슈는 아직 열려 있다"고 적혀 있어도, 실제로 이슈 상태를 조회해서 여전히 열려 있는지 확인한 다음에 움직이는 식입니다. 단순한 사실(파일이 어디 있다 정도)까지 매번 검증할 필요는 없지만, 정책이나 임계값처럼 바뀔 가능성이 있는 정보는 검증을 거치는 게 낫습니다. 그리고 검증해서 틀렸다는 게 확인되면 그 자리에서 메모리 파일도 고쳐둡니다. 안 고치면 다음 세션에서 같은 틀린 전제를 또 주입받게 됩니다.
실사례 — CI 실패를 디버깅할 때의 순서
컨텍스트가 제일 쉽게 터지는 상황이 셀프호스팅 GitLab CI 파이프라인이 실패했을 때입니다. 파이프라인 로그에는 의존성 설치, 빌드, 타입 체크, 배포 단계가 전부 들어 있어서 통째로 가져오면 수천 줄이 되는데, 정작 원인은 실패한 잡 하나의 마지막 몇십 줄에 있는 경우가 대부분입니다.
그래서 순서를 고정해뒀습니다. 먼저 파이프라인 상태만 조회해서 어느 잡이 실패했는지 확인하고, 그 잡의 로그만, 그중에서도 뒷부분만 잘라서 봅니다. 원인이 코드 쪽으로 좁혀지면 그때 관련 파일을 grep 으로 찾아 해당 함수 주변만 읽습니다. 이 순서를 지키면 실제로 컨텍스트에 들어오는 건 실패 로그 몇십 줄과 코드 몇십 줄뿐입니다. 반대로 "로그 전체 붙여넣을 테니 알아서 찾아줘"로 시작하면 원인을 찾기도 전에 세션의 상당 부분이 정상 통과한 빌드 로그로 채워집니다.
머지 후 배포 파이프라인이 끝날 때까지 지켜보는 것도 같은 문제를 안고 있습니다. 상태를 확인할 때마다 응답 전체가 대화에 남으면 거의 같은 내용이 수십 번 쌓입니다. 이럴 땐 폴링(주기적 상태 확인)을 스크립트 하나에 맡겨서 최종 결과만 받아오는 편이 낫습니다. 중간 과정은 스크립트 안에서 소비되고, 대화에는 성공/실패와 실패했을 때의 원인 줄만 남습니다.
한 이슈가 끝나는 지점도 이 순서의 일부입니다. 이슈 번호를 브랜치와 커밋에 박고, 구현하고, 검증하고, 머지와 배포까지 확인하면 그 자리에서 /clear 합니다. 처음엔 쌓인 맥락을 버리는 게 아깝게 느껴지지만, 안 비우고 이어가면 지난 이슈의 파일 경로나 실패한 시도가 새 이슈 작업에 조용히 끼어드는 쪽이 더 비쌉니다.
컨텍스트를 아낀다는 건 결국 "얼마나 많이 넣느냐"가 아니라 "지금 이 작업에 진짜 필요한 것만 들어있게 하느냐"의 문제입니다. 파일 덤프 대신 grep, 긴 로그 대신 요약, 광역 탐색 대신 서브에이전트 위임, 큰 출력 대신 파일 경로, 그리고 이슈 하나가 끝나면 /clear. 이 습관들을 지키는 것만으로도 세션이 길어져도 결과물 품질이 크게 흔들리지 않는다는 걸 체감했습니다.
서브에이전트를 여러 개 동시에 굴리는 병렬 처리 이야기는 3편에서 이어집니다.