재현 → 최소화 → 가설 → 회귀 테스트 → 좁은 수정 → 검증의 반복 루프로 Claude Code를 활용한 실용적인 버그 트리아지 방법.

모든 리포트가 일회성 미스터리처럼 느껴지면 버그는 무작위로 보입니다. 코드를 살짝 건드리고 몇 가지 아이디어를 시도해 보고 해결되길 바라죠. 가끔 사라지기도 하지만 배운 건 적고 같은 문제가 다른 형태로 다시 나타납니다.
버그 트리아지는 그 반대입니다. 불확실성을 빠르게 줄이는 방식입니다. 목표는 모든 것을 즉시 고치는 게 아니라, 모호한 불만을 명확하고 테스트 가능한 문장으로 바꾸고 그 문장이 더 이상 참이 아님을 증명하는 가장 작은 변경을 하는 것입니다.
그래서 이 루프가 중요합니다: 재현, 최소화, 증거로 가능한 원인 식별, 회귀 테스트 추가, 좁은 수정 구현, 검증. 각 단계는 특정 종류의 추측을 제거합니다. 단계를 건너뛰면 나중에 더 큰 수정, 부작용, 혹은 실제로 고쳐지지 않은 "고쳐진" 버그로 대가를 치를 가능성이 큽니다.
현실적인 예를 들어보겠습니다. 사용자가 "저장 버튼이 가끔 아무 동작도 하지 않는다"고 말합니다. 루프 없이라면 UI 코드를 뒤져 타이밍, 상태, 네트워크 호출을 바꿔볼 수 있습니다. 루프이 있다면 먼저 "가끔"을 "다음 정확한 조건에서 항상"으로 바꿉니다. 예: "제목을 편집한 뒤 빠르게 탭을 전환하면 저장이 비활성 상태로 남는다." 그 문장 하나만으로도 진전입니다.
Claude Code는 생각하는 부분을 빠르게 해줄 수 있습니다: 리포트를 정밀한 가설로 바꾸고, 어디를 봐야 할지 제안하며 실패할 최소한의 테스트를 제안합니다. 코드, 로그, 최근 diff를 스캔해 그럴듯한 설명을 빠르게 만들어내는 데 특히 유용합니다.
그래도 중요한 것은 직접 검증하는 것입니다. 환경에서 버그가 실제인지 확인하세요. 그럴듯한 이야깃거리보다 증거(로그, 트레이스, 실패하는 테스트)를 선호하세요. 수정을 가능한 한 작게 유지하고 회귀 테스트로 증명하며, 명확한 체크로 검증해 다른 버그를 만들지 않도록 하세요.
성과는 설명할 수 있고 방어할 수 있으며 회귀를 막을 수 있는 작고 안전한 수정입니다.
좋은 수정은 깔끔한 작업공간과 단일 명확한 문제 진술에서 시작합니다. Claude에게 묻기 전에 하나의 리포트를 골라 다음 문장으로 다시 쓰세요:
"내가 X를 하면 Y를 기대하는데 Z가 발생한다."
그 문장을 쓸 수 없다면 아직 버그가 있는 것이 아닙니다. 미스터리가 있는 것입니다.
처음부터 기본 정보를 수집해 반복적으로 되돌아보는 일을 줄이세요. 이 정보들이 제안을 테스트 가능하게 만듭니다: 앱 버전 또는 커밋(로컬/스테이징/프로덕션 여부), 환경 세부사항(OS, 브라우저/기기, 기능 플래그, 지역), 정확한 입력(폼 필드, API 페이로드, 사용자 행동), 누가 보는지(모두, 특정 역할, 단일 계정/테넌트), 그리고 "기대"의 의미(문구, UI 상태, 상태 코드, 비즈니스 규칙).
그리고 증거를 신선할 때 보존하세요. 단일 타임스탬프가 몇 시간을 절약할 수 있습니다. 이벤트 주변의 로그(가능하면 클라이언트와 서버 모두), 스크린샷이나 짧은 녹화, 요청 ID나 트레이스 ID, 정확한 타임스탬프(타임존 포함), 문제를 유발하는 가장 작은 데이터 스니펫을 캡처하세요.
예: Koder.ai로 생성한 React 앱에서 "결제 성공"이 표시되지만 주문 상태는 "Pending"으로 남아 있는 경우, 사용자 역할, 정확한 주문 ID, API 응답 본문, 해당 요청 ID의 서버 로그 라인을 적어두세요. 이제 Claude에게 한 흐름에 집중하라고 요청할 수 있습니다.
마지막으로 중단 규칙을 정하세요. 코드를 건드리기 전에 무엇이 "고쳐진" 것으로 간주될지 결정하세요: 특정 테스트가 통과하는 것, UI 상태가 바뀌는 것, 로그에서 오류가 더 이상 나타나지 않는 것, 그리고 매번 실행할 짧은 검증 체크리스트 등을 정하세요. 이 규칙은 증상만 고치고 새 버그를 내보내는 일을 막아줍니다.
엉성한 버그 리포트는 사실, 추측, 감정을 섞어둡니다. 도움을 청하기 전에 Claude가 증거로 대답할 수 있는 명확한 질문으로 바꾸세요.
기능과 실패를 이름으로 부르는 한 문장 요약으로 시작하세요. 좋은 예: "초안 저장 시 모바일에서 가끔 제목이 삭제된다." 나쁜 예: "초안이 망가졌다." 이 한 문장이 트리아지 스레드 전체의 닻이 됩니다.
그다음 관찰한 것과 기대한 것을 분리하세요. 지루하고 구체적으로: 클릭한 정확한 버튼, 화면의 메시지, 로그 라인, 타임스탬프, 기기, 브라우저, 브랜치, 커밋. 아직 없다면 그 사실을 적으세요.
붙여넣기 가능한 간단한 구조:
세부사항이 빠져 있으면 예/아니오 질문으로 물어 빠르게 답하게 하세요: 새 계정에서 발생하는가? 모바일에서만 발생하는가? 새 릴리스 이후 시작되었는가? 시크릿 모드에서 재현 가능한가?
Claude는 또한 "리포트 클리너"로 유용합니다. 원본 리포트(스크린샷 복사 텍스트, 로그, 채팅 스니펫 포함)를 붙여넣고 다음을 요청하세요:
"이것을 구조화된 체크리스트로 다시 써주세요. 모순을 표시하고, 빠진 사실 상위 5개를 예/아니오 질문으로 나열하세요. 아직 원인 추정은 하지 마세요."
동료가 "무작위로 실패한다"고 하면 테스트 가능한 문장으로 밀어붙이세요: "iPhone 14, iOS 17.2에서 Save를 빠르게 두 번 탭하면 10번 중 2번 실패한다." 이제 의도적으로 재현할 수 있습니다.
버그를 원하는 때에 발생시키지 못하면 다음 단계는 모두 추측입니다.
문제를 여전히 보여주는 가장 작은 환경에서 재현을 시작하세요: 로컬 개발 빌드, 최소 브랜치, 작은 데이터셋, 켜는 서비스는 최소화.
정확한 단계를 적어 다른 사람이 질문하지 않아도 따라 할 수 있게 하세요. 복사-붙여넣기 가능한 형태로: 명령어, ID, 샘플 페이로드를 정확히 포함합니다.
간단한 캡처 템플릿:
빈도는 전략을 바꿉니다. "항상" 발생하는 버그는 빠른 반복에 좋습니다. "가끔" 발생하는 버그는 타이밍, 캐시, 경쟁 상태 또는 숨은 상태를 가리키는 경우가 많습니다.
재현 노트를 확보한 후 Claude에게 앱을 다시 쓰지 않고 불확실성을 줄일 수 있는 빠른 탐사(probe)를 요청하세요. 좋은 탐사는 작습니다: 실패 경계 주변의 한 줄 로그(입력, 출력, 핵심 상태), 단일 컴포넌트의 디버그 플래그, 결정적 동작을 강제하는 방법(고정 시드, 고정 시간, 단일 워커), 문제를 유발하는 작은 시드 데이터셋, 또는 재생 가능한 단일 요청/응답 쌍 등입니다.
예: 가입 흐름이 "가끔" 실패하면 Claude는 생성된 사용자 ID, 이메일 정규화 결과, 유니크 제약 에러 세부사항을 로그하라고 제안하고 동일한 페이로드를 10번 다시 실행해보라고 할 수 있습니다. 실패가 배포 직후 첫 실행에서만 발생한다면 마이그레이션, 캐시 워밍업, 누락된 시드 데이터를 확인하라는 강력한 단서입니다.
좋은 재현은 쓸모 있습니다. 최소 재현은 강력합니다. 버그를 더 빨리 이해하게 하고 디버깅을 쉽게 하며 "우연히 고쳐진" 상황을 줄입니다.
필수적이지 않은 것은 모두 제거하세요. 긴 UI 흐름 후에 버그가 나타나면 여전히 트리거되는 가장 짧은 경로를 찾으세요. 선택적 화면, 기능 플래그, 관련 없는 통합을 제거해 버그가 사라지는지(중요한 것을 제거한 것) 아니면 유지되는지(노이즈를 찾은 것)를 확인합니다.
그다음 데이터도 줄이세요. 버그가 큰 페이로드를 필요로 하면 여전히 깨지는 가장 작은 페이로드를 찾으세요. 500개의 아이템이 필요하면 5개, 그다음 2개, 그다음 1개로 줄여 보세요. 필드를 하나씩 제거하세요. 목표는 버그를 재현하는 데 필요한 가장 적은 구성요소입니다.
실용적 방법은 "절반을 제거하고 재테스트"입니다:
예: 쿠폰 적용 시 결제 페이지가 "가끔" 충돌한다면, 카트에 할인된 아이템이 하나 이상 있어야 하고 쿠폰이 소문자이며 배송이 "픽업"으로 설정되어야만 실패하는 것을 발견할 수 있습니다. 그게 최소 케이스입니다: 할인된 아이템 1개, 소문자 쿠폰 1개, 픽업 옵션 1개.
최소 케이스가 명확해지면 Claude에게 이를 작은 재현 스캐폴드로 바꿔 달라고 하세요: 실패하는 함수를 최소 입력으로 호출하는 작은 테스트, 축소된 페이로드로 한 엔드포인트를 호출하는 짧은 스크립트, 한 경로를 방문해 하나의 동작만 수행하는 작은 UI 테스트 등.
문제를 재현할 수 있고 작은 테스트 케이스가 있으면 추측을 멈추세요. 목표는 그럴듯한 원인 목록을 짧게 만들고 각 항목을 증명하거나 반박하는 것입니다.
유용한 규칙은 가설을 세 개로 유지하는 것입니다. 세 개 이상이면 테스트 케이스가 아직 too big하거나 관찰이 모호한 것입니다.
증상을 컴포넌트에 매핑하세요.
관찰한 것을 어디에서 발생할 수 있는지로 번역합니다. UI 증상이 항상 UI 버그를 의미하지는 않습니다.
예: React 페이지에 "Saved" 토스트가 보이지만 레코드가 나중에 없으면 (1) UI 상태, (2) API 동작, (3) 데이터베이스 쓰기 경로를 의심할 수 있습니다.
각 가설에 대한 증거를 만드세요.
Claude에게 먼저 그럴듯한 실패 모드를 평이한 언어로 설명해 달라고 하고, 각 항목을 확증할 증거가 무엇인지 물어보세요. 목표는 "어쩌면"을 "이걸 정확히 확인하라"로 바꾸는 것입니다.
세 가지 흔한 가설과 수집할 증거 예시:
노트는 간결하게 유지하세요: 증상, 가설, 증거, 판단. 한 가설이 사실과 맞으면 회귀 테스트를 고정하고 필요한 것만 고치세요.
좋은 회귀 테스트는 안전벨트입니다. 버그가 존재했음을 증명하고 진짜로 고쳐졌는지 알려줍니다.
먼저 실제 실패와 일치하는 가장 작은 테스트를 선택하세요. 버그가 여러 부품이 함께 동작할 때만 나타나면 단위 테스트는 놓칠 수 있습니다.
단일 함수의 반환이 잘못되면 단위 테스트를, 부품 경계 문제이면 통합 테스트를, 전체 사용자 흐름이 필요하면 엔드투엔드를 사용하세요.
Claude에게 무엇이든 쓰게 하기 전에 최소화된 케이스를 엄격한 기대 동작으로 다시 진술하세요. 예: "사용자가 빈 제목을 저장하면 API는 400과 'title required' 메시지를 반환해야 한다." 이제 테스트는 명확한 목표를 가집니다.
그다음 Claude에게 먼저 실패하는 테스트 초안을 작성해 달라고 하세요. 설정은 최소로 유지하고 버그를 유발하는 데이터만 복사하세요. 테스트 이름은 내부 함수가 아니라 사용자가 경험하는 내용으로 하세요.
자체적으로 빠르게 확인하세요:
테스트가 의도한 이유로 실패하면 좁은 수정으로 진행할 수 있습니다.
작은 재현과 실패하는 회귀 테스트가 있으면 "정리" 욕구를 억누르세요. 목표는 테스트를 올바른 이유로 통과하게 하는 가장 작은 변경을 하는 것입니다.
좋은 좁은 수정은 표면적 영향을 최소화합니다. 실패가 한 함수에 있으면 그 함수만 고치세요. 경계 체크가 빠졌다면 호출 체인 전체가 아니라 경계에서 체크를 추가하세요.
Claude를 보조 도구로 사용할 때는 두 가지 수정 옵션을 요청하고 범위와 위험을 비교하세요. 예: React 폼이 빈 필드 때문에 크래시하면:
옵션 A가 보통 트리아지 선택입니다: 더 작고, 리뷰가 쉽고, 다른 것을 깨뜨릴 가능성이 적습니다.
수정을 좁게 유지하려면 가능한 한 적은 파일만 건드리고 리팩토링보다 지역적 수정을 선호하며, 나쁜 값이 들어오는 지점에 가드와 유효성 검사를 추가하고 단 하나의 명확한 전후 행동 변화를 만드세요. 이유가 명백하지 않을 때만 주석을 남기세요.
구체적 예: Go API 엔드포인트가 선택적 쿼리 파라미터가 없을 때 패닉이 난다면 좁은 수정은 핸들러 경계에서 빈 문자열을 처리(기본값으로 파싱하거나 400 반환)하는 것입니다. 회귀 테스트가 공유 코드에 문제가 있음을 증명하지 않는 한 공용 파싱 유틸리티를 변경하지 마세요.
변경 후 실패하던 테스트와 인접한 테스트를 한두 개 재실행하세요. 관련 없는 테스트를 많이 업데이트해야 하면 변경이 너무 광범위하다는 신호입니다.
검증 단계에서는 단일 테스트를 통과하지만 인접 경로를 깨트리거나 에러 메시지를 바꾸거나 느린 쿼리를 추가하는 등 놓치기 쉬운 문제를 잡습니다.
먼저 추가한 회귀 테스트를 다시 실행하세요. 통과하면 가장 가까운 이웃들: 같은 파일의 테스트, 같은 모듈의 테스트, 동일한 입력을 다루는 테스트를 실행하세요. 버그는 공용 헬퍼, 파싱, 경계 검사, 캐시에서 숨는 경우가 많으므로 관련 실패는 가까운 곳에서 나타납니다.
그다음 원래 리포트의 단계로 빠른 수동 확인을 하세요. 짧고 구체적으로: 같은 환경, 같은 데이터, 같은 클릭/요청 순서. 리포트가 모호했다면 재현에 사용한 정확한 시나리오로 테스트하세요.
집중을 유지하는 데 도움이 필요하면 변경한 파일, 의도한 동작, 영향을 받을 수 있는 항목을 공유하고 Claude에게 짧은 검증 계획(단위, 통합, 수동 검사 포함)을 요청하세요. 최고의 계획은 짧고 실행 가능하며(5~8개의 체크), 각 항목에 명확한 합격/불합격 기준이 있습니다.
마지막으로 PR이나 노트에 무엇을 검증했는지 기록하세요: 어떤 테스트를 실행했고 어떤 수동 단계를 시도했으며 어떤 범위는 테스트하지 않았는지(예: "모바일 미검증")를 적어두면 수정이 더 신뢰받기 쉽습니다.
문제를 즉시 재현할 수 없는데 "수정"을 받아들이는 것이 가장 시간을 낭비하는 방법입니다. 재현을 안정적으로 만들 수 없다면 무엇이 실제로 개선되었는지 알 수 없습니다.
실용적 규칙: 정확한 단계(정확한 입력, 환경, 잘못된 상태 정의)를 설명할 수 있을 때까지 수정 요청을 하지 마세요. 리포트가 모호하면 처음 몇 분은 그것을 두 번 실행해 같은 결과가 나오는 체크리스트로 바꾸는 데 쓰세요.
재현 불가한 상태에서 수정하기. 최소한의 "항상 실패하는" 스크립트나 단계 집합을 요구하세요. "가끔"이면 타이밍, 데이터 크기, 기능 플래그, 로그를 캡처해 무작위성이 사라질 때까지 조사하세요.
너무 일찍 최소화하기. 원본 실패를 확정하기 전에 케이스를 지나치게 줄이면 신호를 잃을 수 있습니다. 먼저 기준 재현을 고정한 다음 변경을 하나씩 줄이세요.
Claude에게 추측을 맡기기. Claude는 가능한 원인을 제안할 수 있지만 증거가 필요합니다. 2~3개의 가설과 각 가설을 확증/기각할 정확한 관찰(한 줄 로그, 브레이크포인트, 쿼리 결과)을 요청하세요.
잘못된 이유로 통과하는 회귀 테스트. 테스트가 실패 경로를 전혀 타지 않아서 "통과"할 수 있습니다. 수정 전에는 테스트가 실패하는지, 그리고 실패 이유가 예상한 것인지 확인하세요.
증상이 아닌 트리거를 건드리기. 널 체크를 추가했지만 실제 문제는 "이 값은 절대 null이 되어선 안 된다"라면 더 깊은 버그를 숨길 수 있습니다. 나쁜 상태를 만드는 조건을 고치는 것을 선호하세요.
변경 전후로 새 회귀 테스트와 원래 재현 단계를 실행하세요. 예: 프로모 코드가 적용된 후 배송 변경 때만 발생하는 체크아웃 버그라면 전체 시퀀스를 "진실"로 유지하세요. 최소화된 테스트는 더 작아도 실제 전체 시퀀스는 보존하세요.
검증이 "지금 보기에는 좋아 보임"에 의존한다면 한 가지 구체적 체크(로그, 메트릭, 특정 출력)를 추가해 다음 사람이 빠르게 확인할 수 있게 하세요.
시간 압박이 있을 때도 작고 반복 가능한 루프가 영웅적 디버깅보다 낫습니다.
최종 결정을 몇 줄로 작성해 다음 사람(종종 미래의 당신)이 신뢰할 수 있게 하세요. 유용한 형식: "근본 원인: X. 트리거: Y. 수정: Z. 안전한 이유: W. 변경하지 않은 것: Q."
다음 단계: 자동화할 수 있는 것을 자동화하세요(저장된 재현 스크립트, 표준 테스트 명령, 근본 원인 노트 템플릿).
Koder.ai(Koder.ai)를 사용해 앱을 빌드한다면 Planning Mode가 코드를 건드리기 전에 변경 계획을 정리하는 데 도움이 되고 스냅샷/롤백은 까다로운 재현 과정을 실험하면서 안전하게 작업하기 쉽게 합니다. 수정이 검증되면 소스 코드를 내보내거나 배포하고 맞춤 도메인을 포함해 호스팅할 수 있습니다.
Bug triage는 모호한 리포트를 명확하고 테스트 가능한 진술로 바꾸고, 그 진술이 더 이상 참이 아님을 증명하는 가장 작은 변경을 하는 습관입니다.
전체를 한 번에 고치는 것이 목적이 아니라 불확실성을 단계적으로 줄이는 것이 목적입니다: 재현, 최소화, 증거 기반 가설 수립, 회귀 테스트 추가, 좁은 범위의 수정, 검증.
각 단계가 다른 종류의 추측을 제거하기 때문에 중요합니다.
다음 문장으로 다시 쓰세요: “When I do X, I expect Y, but I get Z.”
그다음 테스트 가능하도록 필요한 최소한의 컨텍스트를 모으세요:
문제를 재현 가능한 가장 작은 환경(보통 로컬 개발 + 작은 데이터셋)에서 재현할 수 있는지 확인하세요.
“가끔” 발생한다면 변수를 통제해 결정적으로 만들려고 해보세요:
실패를 즉시 재현할 수 있을 때까지 진행하지 마세요. 그렇지 않으면 그냥 추측하는 겁니다.
최소 재현은 필요 없는 모든 것을 제거하면서도 버그가 유지되도록 하는 것입니다.
실용적인 방법은 “절반 제거하고 재테스트”입니다:
단계(짧은 사용자 흐름)와 데이터(작은 페이로드, 적은 항목 수)를 모두 줄여 가장 작은 반복 가능한 트리거를 찾으세요.
재현과 최소 재현을 확보한 후에는 추측을 멈추고 2–3개의 그럴듯한 원인 가설로 좁히세요. 각 가설을 증명하거나 반박할 증거를 수집합니다.
예: UI에 "Saved" 토스트가 표시되지만 레코드가 나중에 없는 경우는 (1) UI 상태, (2) API 동작, (3) DB 쓰기 경로를 의심할 수 있습니다.
각 가설마다 어떤 증거가 확증이 될지 정하세요(한 줄의 로그, 브레이크포인트, 쿼리 결과 등). 증거를 모아 판단을 내리면 불필요한 수정을 피할 수 있습니다.
적절한 레벨의 테스트를 선택하세요:
좋은 회귀 테스트는 현재 코드에서 의도한 이유로 실패해야 합니다. 구체적인 어설션(상태 코드, 메시지, 렌더링 텍스트)을 사용하고 하나의 버그만 다루게 하세요.
테스트가 실패하던 이유 때문에 테스트가 통과되도록 하는 가장 작은 변경이 좋은 좁은 수정입니다.
원칙:
수정 후 실패하던 테스트와 인접한 테스트 몇 개만 실행해 지나치게 광범위한 변경이 아닌지 확인하세요.
검증은 수정이 한 가지 테스트를 통과하는 것에 그치지 않고 인접한 경로를 깨트리지 않았는지 확인하는 단계입니다.
간단한 검증 체크리스트:
PR이나 노트에 어떤 테스트와 수동 검사를 했는지 기록해 두면 다음 사람이 신뢰하기 쉽습니다.
일반적으로 세 가지 가설로 유지하세요. 그보다 많으면 재현 케이스가 아직 너무 크거나 관찰이 모호한 경우입니다.
각 가설에 대해:
이 방식은 진행을 촉진하고 끝없는 "아마도 X일 것이다" 디버깅을 막아줍니다.