root-cause-first · git:20260915.1b5578d · 2026-09-15 · sha256 c36fbb25fdb30fc3
root-cause-first git:20260915.1b5578dA
Immutable. This exact content is served forever at /api/v1/blob/c36fbb25fdb30fc3.
--- name: root-cause-first description: "버그, 테스트 실패, CI 빨강, 런타임 예외를 만났을 때 고치기 전에 원인부터 밝히는 네 단계(재현, 원인, 가설, 수정)와 멈춰야 하는 신호. 증상만 가리는 수정(카운터·로그만 추가, 문자열 분류에 경우 하나 더 붙이기, 같은 수정을 자리마다 따로 붙이기, 상한·재시도 간격으로 누르기)을 고르기 직전에 쓴다. 컴파일러가 정확히 짚은 오타 한 줄처럼 원인이 이미 보이는 수정에는 쓰지 않는다." --- # 근본 원인 먼저 ## 언제 쓰나 - 테스트가 빨갛다. 빌드는 되는데 스위트가 실패한다. 기본 브랜치가 안 빌드된다. - 런타임에 예외가 뜬다. 턴이 죽는다. 판정 레인이 거절한다. - "어제까지 됐는데 안 된다." 그 사이 누가 무엇을 바꿨는지 모른다. 원인이 이미 보이는 수정에는 쓰지 않는다. 오타 하나, 빠진 구분자, 컴파일러가 정확히 짚어 준 한 줄은 그냥 고친다. ## 원칙 **원인을 밝히기 전에 고치지 않는다.** 증상이 사라지는 것과 원인이 사라지는 것은 다르다. 인증이 안 된다고 인증을 끄는 것은 해결이 아니다. ## 네 단계 순서대로 밟는다. 건너뛰었으면 처음으로 돌아간다. 1. **재현.** 실패를 한 번 직접 낸다. 무엇이 들어가면 무엇이 나오는지 정확히 적는다. 재현이 안 되면 아직 디버깅이 아니라 관찰이다. 관찰 시간을 늘려 다시 본다. 2. **원인.** 증상에서 거슬러 올라간다. 스택을 따라가고, 마지막으로 됐던 커밋과 지금을 `git log --oneline` 으로 가른다. "여기서 틀렸다"가 아니라 "여기서부터 틀리기 시작했다"를 찾는다. 3. **가설.** 원인을 한 문장으로 쓰고, 그게 맞다면 무엇을 바꿨을 때 무엇이 달라지는지 예측한다. 예측 없는 수정은 추측이다. 4. **수정.** 가설이 가리킨 자리만 고친다. 1의 재현을 다시 돌려 증상이 사라졌는지, 다른 것이 깨지지 않았는지 본다. ## 멈추고 처음으로 돌아가는 신호 - 같은 자리를 두 번째 고치고 있다 → 호출하는 쪽을 하나씩 막는 것을 멈추고, 그 실수가 생길 수 없게 만드는 한 곳(타입, 공용 함수, 검사 도구)을 찾는다. - "일단 이렇게 넘기고"라고 쓰고 있다 → 그게 증상 가리기다. 왜 넘어가야 하는지부터 본다. - 수정이 처음 짚은 자리 밖으로 번진다 → 원인을 잘못 짚었을 수 있다. 2로 돌아간다. - 고친 결과가 "이제 실패가 보인다"뿐이다 → 카운터나 로그는 경보다. 실패는 그대로 난다. - 문자열에서 단어를 찾아 분기를 하나 더 붙이고 있다 → 다음에 올 새 문구를 또 놓친다. 값의 종류를 닫힌 타입으로 나눌 수 있는지 먼저 본다. - 상한, 재시도 간격, 중복 제거로 증상 횟수를 줄이고 있다 → 왜 반복되는지가 원인이다. ## 2단계가 막힐 때 — `references/root-cause-tracing.md` 스택이 깊어 어디서부터 틀렸는지 안 잡히면 이 파일을 연다. `keeper_skill` 을 같은 `identity` 에 `file = "references/root-cause-tracing.md"` 로 부른다. 호출 사슬을 거꾸로 따라가 처음 잘못된 값이 들어온 곳을 찾는 절차가 예시와 함께 있다.