root-cause-first · git:20261013.f2be3c7 · 2026-10-13 · sha256 33fd1ece0bd43e68

root-cause-first git:20261013.f2be3c7A

Immutable. This exact content never changes and is served at /api/v1/blob/33fd1ece0bd43e68.

---
name: root-cause-first
description: "버그·테스트 실패·런타임 예외에서 다음 행동을 고를 때, 실패 증거와 가설을 연결해 원인을 좁히고 수정한다. 이미 확보한 재현·로그를 재사용하고, 가설을 구분하는 관측만 추가한다. 원인이 분명한 오타 수정에는 생략한다. 환경 준비가 막히면 원래 작업의 완료 조건을 보존한 채 환경 문제를 분리한다. 같은 실패를 조건 변화 없이 반복하거나, 로그·문자열 분기·상한으로 증상만 가리려 할 때 사용한다."
---

# 근본 원인 먼저

## 언제 쓰나

- 테스트가 빨갛다. 빌드는 되는데 스위트가 실패한다. 기본 브랜치가 안 빌드된다.
- 런타임에 예외가 뜬다. 턴이 죽는다. 판정 레인이 거절한다.
- "어제까지 됐는데 안 된다." 그 사이 누가 무엇을 바꿨는지 모른다.

원인이 이미 보이는 수정에는 쓰지 않는다. 오타 하나, 빠진 구분자, 컴파일러가 정확히
짚어 준 한 줄은 그냥 고친다.

## 원칙

**원인을 밝히기 전에 고치지 않는다.** 증상이 사라지는 것과 원인이 사라지는 것은 다르다.
인증이 안 된다고 인증을 끄는 것은 해결이 아니다.

## 네 단계

이미 확보한 증거에서 이어간다. 부족한 단계만 보완한다.

1. **실패 증거.** 입력, 기대 결과, 실제 오류와 환경을 적는다. 기존 로그나 재현이
   같은 조건을 다루면 재사용한다. 재현이 안 되면 가설을 구분할 입력이나 관측을
   고른다. 시간만 늘려 같은 동작을 반복하지 않는다.
2. **원인.** 증상에서 거슬러 올라간다. 스택을 따라가고, 마지막으로 됐던 커밋과 지금을
   `git log --oneline` 으로 가른다. "여기서 틀렸다"가 아니라 "여기서부터 틀리기
   시작했다"를 찾는다.
3. **가설.** 원인을 한 문장으로 쓰고, 그게 맞다면 무엇을 바꿨을 때 무엇이 달라지는지
   예측한다. 예측 없는 수정은 추측이다.
4. **수정.** 가설이 가리킨 자리만 고친다. 1의 재현을 다시 돌려 증상이 사라졌는지,
   다른 것이 깨지지 않았는지 본다.

환경 준비가 원래 작업을 막으면 오류·재현 명령·작업 경로와 필요한 수리를 분리해
기록하고 담당 Keeper에게 전달한다. 원래 완료 조건과 다음 단계는 보존한다.
수리가 끝나면 원래 작업의 남은 단계에서 이어간다.

## 가설을 다시 확인할 신호

- 같은 조건에서 같은 수정이 실패한다 → 호출하는 쪽을 하나씩 막는 것을 멈추고, 그 실수가
  생길 수 없게 만드는 한 곳(타입, 공용 함수, 검사 도구)을 찾는다.
- "일단 이렇게 넘기고"라고 쓰고 있다 → 그게 증상 가리기다. 왜 넘어가야 하는지부터 본다.
- 수정 범위가 넓어진다 → 같은 원인을 고치는 데 필요한지 확인한다. 새 증거가
  가설을 뒤집었다면 가설을 수정하고, 그대로 유효한 증거는 보존한다.
- 고친 결과가 "이제 실패가 보인다"뿐이다 → 카운터나 로그는 경보다. 실패는 그대로 난다.
- 문자열에서 단어를 찾아 분기를 하나 더 붙이고 있다 → 다음에 올 새 문구를 또 놓친다.
  값의 종류를 닫힌 타입으로 나눌 수 있는지 먼저 본다.
- 상한, 재시도 간격, 중복 제거로 증상 횟수를 줄이고 있다 → 왜 반복되는지가 원인이다.

## 2단계가 막힐 때 — `references/root-cause-tracing.md`

스택이 깊어 어디서부터 틀렸는지 안 잡히면 이 파일을 연다. `keeper_skill` 을 같은
`identity` 에 `file = "references/root-cause-tracing.md"` 로 부른다. 호출 사슬을 거꾸로
따라가 처음 잘못된 값이 들어온 곳을 찾는 절차가 예시와 함께 있다.