Anthropic이 Claude Code로 대규모 코드 마이그레이션을 실행하는 법
다년(多年) 프로젝트였던 코드 마이그레이션이 몇 주로 압축됐습니다. 지난 한 달간 Anthropic의 개발자들은 Claude Fable 5, Opus 4.8, 그리고 동적 워크플로우를 이용해 수만~수십만 줄에 이르는 10개 코드 패키지를 마이그레이션했습니다. 그중 두 사례와 실전 베스트 프랙티스를 정리합니다.
"코드를 고치지 마라. 그 코드를 만들어낸 프로세스(루프)를 고쳐라."
이 글의 핵심 통찰. 개별 파일의 오류를 손으로 패치하는 대신, 오류를 만들어낸 규칙과 검증 루프를 고치면 에이전트 군단이 나머지를 스스로 정리한다.
1. 두 개의 마이그레이션
접근 방식은 달랐지만, 둘 다 "멀티 에이전트 루프 + 기계적 검증" 이라는 같은 뼈대를 공유했습니다.
Jarred Sumner — Bun
Zig → Rust, 100만 줄
Bun의 공동 창업자가 2주도 안 되어 100만 줄을 생성. 머지 전 CI에서 기존 테스트 스위트 100% 통과. 머지 후 드러난 19개 회귀는 모두 수정 완료. 6월 Claude Code 안에서 배포됐다. 구조를 보존하는(structure- preserving) 번역 방식.
Mike Krieger — Anthropic Labs
Python → TypeScript, 16.5만 줄
주말 동안 수백 개의 에이전트, 8개의 페이즈 게이트, 3라운드의 적대적 리뷰, 그리고 모든 명령의 출력을 Python 원본과 비교하는 최종 패리티 검사로 완주. 구조를 재설계(redesign)하는 방식.
2. 왜, 그리고 언제 언어를 바꾸는가
마이그레이션은 초기 설계와 현재 상황 사이의 간극에서 시작됩니다. 알려진 트레이드오프가 발목을 잡거나, 더 나은 방법이 등장했거나, 원래 생태계가 쪼그라들 때입니다. Jarred가 Zig를 고른 건 2026년 이전, LLM도 없던 시절 "오클랜드의 비좁은 아파트에서 1년 만에 Bun을 쓰기" 위한 선택이었죠. 이제 Bun CLI는 월 1,000만 회 이상 다운로드되고 Claude Code 내부에서도 쓰입니다.
최악의 시나리오가 바뀌었다
예전 마이그레이션의 커리어 리스크는 컸습니다. 두 코드베이스를 분기~수년 병행 유지하다가 결과가 90% 패리티에 그치면, 시작할 때보다 더 큰 골칫거리가 됐죠. 이제 최악의 경우는 브랜치를 지우고 다시 시도하는 것입니다.
비용
Bun 마이그레이션은 캐시되지 않은 입력 토큰 59억 개 + 출력 토큰 6.9억 개를 소비 — API 가격 기준 약 $165,000. Mike의 본 작업은 2,700만 토큰. 4년/300~400만 달러 프로젝트는 아니지만, 정당한 비즈니스 근거는 여전히 필요하다.
동기가 된 컴파일 시간
Mike의 팀 도구는 단일 바이너리로 배포된다. Python 툴체인으로는 플랫폼당 약 8분, 빌드 매트릭스 전체로 30분이 걸렸다. 포팅 후 같은 컴파일이 약 2초, 바이너리 시작이 6배 빨라졌고, 별도 배포 파이프라인을 폐기했다.
3. 왜 AI가 마이그레이션의 셈법을 바꾸는가
Fable 5와 Opus 4.8은 서브에이전트로 병렬 워크스트림을 위임·지휘·검증하고, 목표를 향한 여러 경로를 찾는 데 특히 강합니다. 대규모 코드 마이그레이션이 이 모델들에 효과적인 이유는 다음과 같습니다.
작업이 병렬적이다
파일·크레이트 같은 수천 개의 독립 단위로 나뉘어, 에이전트들이 서로를 기다리지 않고 동시에 작업한다.
컨텍스트가 명확하다
기존 코드가 곧 스펙이다. 번역 에이전트가 따를 가이드를 만드는 핵심 레퍼런스가 된다.
심판이 내장돼 있다
테스트 스위트가 검증의 기준이 된다. 검증이 객관적일수록 모델은 사람 개입 없이 며칠씩 정답에 수렴한다.
큐가 스스로 쌓인다
컴파일러나 테스트가 실패하면, 그 자체가 다음 에이전트가 처리할 작업 항목이 된다.
드리프트가 숨을 곳이 없도록 설계한다. 리뷰어는 모든 지적에 근거가 된 규칙을 인용하므로, 위반은 조용한 이탈이 아니라 큐 항목이 된다. 에이전트가 엣지 케이스를 만나면, 그 해결책은 이후 모든 에이전트가 따르는 규칙이 된다.
4. 언어의 간극(Gap)이란 무엇인가
새 언어는 옛 언어와 다른 요구사항을 강제합니다. 이 암묵적 지식을 명시적으로 기록한 것이 "갭 인벤토리"입니다. 두 사례의 핵심 간극을 코드로 비교합니다.
Zig → Rust: 수동 메모리 관리
Zig
fn readConfig(allocator: Allocator) ![]u8 {
const buf = try allocator.alloc(u8, 1024);
// ...fill buf...
return buf; // 주석만이 "호출자가 free 해야 함"을 말한다
}
// defer allocator.free(buf)를 잊어도 컴파일은 된다.
// 누수는 런타임에서야 드러난다.Rust
fn read_config() -> Vec<u8> {
let buf = vec![0u8; 1024];
// ...fill buf...
buf // 소유권이 호출자로 이동, 메모리는 자동 해제
}
// 이동 후 사용? 이중 해제? 둘 다 컴파일 불가.
// free 호출 자체가 없으니 잊을 것도 없다.Python → TypeScript: 인터페이스와 계약
Python
def register(handler):
handler.setup()
return handler.run({"retries": 3})
# .setup()과 .run()을 가진 어떤 객체든 동작한다.
# 실제로 뭐가 넘어오는지? 코드베이스 전체를 읽어야 안다.TypeScript
interface RunResult { ok: boolean }
interface Handler {
setup(): void;
run(opts: { retries: number }): Promise<RunResult>;
}
function register(handler: Handler): Promise<RunResult> {
handler.setup();
return handler.run({ retries: 3 });
}
// 컴파일되려면 계약을 먼저 적어야 한다.5. 대규모 마이그레이션의 6단계
각 단계는 리뷰와 게이트로 연결됩니다. Jarred는 이 흐름을 그대로 따랐고, Mike는 유사한 구조로 전체를 처음부터 끝까지 돌린 뒤 규칙과 워크플로우를 수정해 다시 실행하는 방식을 세 번째 시도까지 반복(매번 결과물은 폐기)했습니다.
사전 조건 — 강한 심판(Judge) 구축
종료 조건이자 성공 척도. 원본 코드와 대상 코드를 동일 기준으로 평가할 수 있어야 한다. 테스트를 분류하고, 이식 가능하게 재작성한 뒤, 일부러 망가뜨린 코드에 돌려 실패하는지 확인해 심판을 검증한다.
룰북·의존성 맵·갭 인벤토리 작성
번역 규칙(룰북), 마이그레이션 순서를 정하는 의존성 맵, 단순 번역이 아닌 재설계가 필요한 지점의 목록(갭 인벤토리)을 만든다. 룰북이 갭 인벤토리보다 먼저다.
규칙 스트레스 테스트 (셰이크다운 크루즈)
소수 파일로 미니 마이그레이션을 돌려 규칙의 결함을 잡는다. Jarred는 1,448개 파일 전체로 퍼지기 전에 두 개의 치명적 이슈를 발견했다. 번역 결과물은 전부 버린다 — 목표는 진척이 아니라 규칙 정제다.
전체 번역 (구현 → 리뷰 → 수정 루프)
작은 모델에 구현을 맡기고, 큰 모델은 리뷰어로 둔다. 큐는 기계적으로: '디스크에 파일이 존재하는가'로 완료를 판정해 언제든 재개 가능하다. 두 명의 적대적 리뷰어가 별도 컨텍스트로 검증하고, 이견은 세 번째 에이전트가 중재한다.
컴파일
오케스트레이터가 워크스페이스 전체에 컴파일러를 한 번 돌리고, 'Fixer 에이전트'가 에러 목록을 병렬로 처리한다. 에러 목록을 검토해 시스템적 문제(예: 순환 임포트 제거 후 쏟아진 수천 개 모듈 에러)를 잡는다.
실행 (스모크 테스트)
스모크 테스트의 크래시가 컴파일러 에러 목록처럼 기계적 기준이 된다. 크래시를 근본 원인별로 묶어 적대적 서브에이전트가 검토한다.
동작 일치 (Behavior parity)
테스트 스위트를 샤딩해 양쪽 코드베이스에 돌린다. 빌드 데몬만이 바이너리를 재빌드할 수 있게 해 가장 비싼 작업을 직렬화한다. 같은 실패가 여러 테스트에서 반복되면, 버그를 만든 규칙을 고치고 해당 파일만 재생성한다.
테스트 스위트가 없어도 이 단계가 막히지 않는다. Mike는 Claude에게 7개의 실제 시나리오를 양쪽 코드베이스에 돌려 결과를 diff하는 스크립트를 만들게 했고, 나아가 Claude가 스스로 E2E 테스트 스위트를 설계해 나흘 밤 연속 자율 실행하며 깨진 것을 고치게 했다. 심판을 물려받을 수 없다면, Claude에게 만들게 하라. 어느 쪽이든 원본 코드베이스가 정답(ground truth)이다.
6. 코드 마이그레이션 베스트 프랙티스
모든 실행이 이전에는 몰랐던 것을 가르쳐 줬지만, 프로젝트를 관통해 유효했던 몇 가지 원칙입니다.
이 가이드를 맹목적으로 따르지 마라
모든 마이그레이션은 다르다. 시작점으로만 삼고, 착수 전에 Claude와 함께 당신만의 마이그레이션을 설계하라.
개별 실패에 매달리지 마라
개별 실패는 루프의 몫이다. Fixer 에이전트가 태워버린다. 당신의 주의는 패턴에 쏟아야 한다.
리뷰는 적대적으로, 검증은 기계적으로
적대적 리뷰는 긴 작업을 가능케 하며 토큰값을 한다. 컴파일러·diff·테스트 스위트 같은 스크립트가 심판이 되게 하라.
모든 곳에 가장 큰 모델을 쓰지 마라
토큰은 루프에 집중된다. 대량의 구현 팬아웃은 작은 모델로, 큰 모델은 리뷰어와 규칙 작성에만 아껴 쓴다.
사람의 시간을 앞단에 몰아라
룰북과 스트레스 테스트가 가장 오래 걸린다. 그 이후는 대부분 큐가 소진되는 과정이다.
작업 큐를 기계적이고 재개 가능하게
'완료'는 '출력 파일이 디스크에 존재함'을 의미해야 한다. 루프의 결과를 리뷰하되, 코드를 리뷰하지 마라.
7. 결과 — 측정 가능하게 더 나은 코드
Jarred의 Bun 마이그레이션은 프로덕션에 올라가 있습니다. 모든 마이그레이션에 트레이드오프는 있습니다 — Rust 코드의 약 4%는 C/C++ 경계의 단일 라인 포인터 연산이 대부분인 unsafe 블록 안에 있습니다. 하지만 새 코드베이스는 명백히 더 낫습니다.
6,745 → 609
2,000회 반복 빌드 벤치마크의 메모리 사용량(MB). 감지 가능한 모든 메모리 누수가 수정됐다.
−19%
Linux·Windows에서의 바이너리 크기 감소.
2–5%
HTTP 서빙과 next build·tsc 같은 실제 워크로드에서의 속도 향상.
오래 미뤄둔 마이그레이션의 셈법을 다시 계산할 때가 됐는지 자문해 볼 만합니다. 그동안 참고 써온 코드베이스를 하나 골라, Claude에게 그 마이그레이션 프로세스가 어떤 모습일지 물어보는 것부터 시작해 보세요.