로컬 AI에게 '지속되지 않은(didn't stick)' 버그 수정 사항을 찾아보라고 요청했습니다. 14개가 플래그 지정되었고, 저는 그중
요약
이 글은 'Ghosts of bugs past'라는 도구를 소개하며, Git 히스토리를 탐색하여 지속되지 못하고 재발하는 버그 수정 패턴을 찾아냅니다. 이 시스템은 `git blame`으로 용의자를 찾고, 로컬에서 실행되는 Gemma 3 모델이 커밋 메시지와 diff를 분석해 코드의 불안정성을 진단합니다.
핵심 포인트
- Git 기능(`git blame`)만으로도 수정 사항의 출처를 파악할 수 있습니다.
- 로컬 LLM(Gemma 3)을 활용하여 커밋 메시지와 diff를 해석하는 것이 핵심입니다.
- '지속되지 않은 버그'를 찾아내어 코드베이스의 불안정성을 시각화합니다.
- 분석 결과는 '묘지 페이지(graveyard page)' 형태로 제공됩니다.
이 글은 Hacktoberfest Open-Source AI Challenge: Ghost in the Machine 에 대한 제출물입니다.
제가 만든 것 (What I Built)
Ghosts of bugs past는 프로젝트의 git 히스토리를 탐색하여 지속되지 않은 버그 수정 사항을 찾아냅니다. 즉, 어떤 버그가 "수정되었다"고 표시된 후 나중에 다시 수정되어야 하는 경우입니다.
버그 중에는 사라지지 않고 남아있는 것들이 있습니다. 커밋 메시지에는 "수정됨(fixed)"이라고 적혀 있지만, 모두가 다음 단계로 넘어가다가 몇 달 후에 같은 문제를 또 다른 커밋으로 수정하는 경우가 있습니다. 여기는 Python HTTP 라이브러리인 requests에서 가져온 실제 사례입니다:
2012-04-12 · 잘못된 Digest 인증 시 무한 루프 수정 (Fix infinite loop on wrong Digest Authentication)
2012-07-14 · 잘못된 Digest 인증 시 무한 루프 수정 (Issue #547) (Fix infinite loop on wrong Digest Authentication (Issue #547))
"기존의 버그가 아닌 현재의 수정 사항을 사용하도록 코드를 수정함."("Modified code to use the current fix versus the old fix, which was broken.")
제목은 같지만, 세 달 간격이 벌어졌습니다. 첫 번째 수정은 지속되지 못했습니다.
작동 방식:
- Git이 용의자를 찾습니다. 무언가를 수정했다고 말하는 모든 커밋에 대해
git blame을 사용하여 어떤 라인을 재작성했는지 확인합니다. 만약 그 라인들이 더 이전 수정 사항에서 비롯된 것이라면, 그 수정 사항은 지속되지 못했을 수 있습니다. 여기에는 AI가 필요하지 않으며, 순전히 git의 기능입니다. - Gemma가 판단합니다. Ollama를 통해 제 PC에서 실행되는 로컬 Gemma 3 모델이 커밋 메시지와 디프(diffs)를 모두 읽고 다음 중 하나를 선택합니다: "같은 버그가 재발함", "수정이 새로운 버그를 유발함", 또는 "다른 문제".
- 순수한 검사가 정직함을 유지하게 합니다. (이것은 제가 필요할 것이라고 예상하지 못했던 부분입니다. 아래를 참조하세요.)
데모 (Demo)
저는 Gemma 3 12B 모델을 사용하여 click과 requests의 상위 12개 용의자들에 대해 테스트를 실행했습니다. 각 실행에는 제 PC에서 약 4~5분이 소요되었습니다. 이 도구는 '묘지 페이지(graveyard page)'를 작성합니다:
여기서 가장 명확하게 발견된 유령(ghost)은 click 라이브러리에서 찾은 것입니다:
BURIED 2026-04-29 19fd4d6 "Fix: Ensure fish completion handles multiline help strings correctly (#3126)"
RETURNED 2026-05-23 c353590 "Fix broken fish completion and multiline help string"
무덤 속에서 23일이 지났습니다. 두 번째 수정은 첫 번째 수정보다 58줄을 더 많이 재작성했습니다.
...
나중 커밋 메시지는 이를 분명히 말합니다: "#3126에 의해 8.4.0 버전에서 도입된 회귀(regression)를 수정하고 #3043도 제대로 수정함". 고쳐지고, 망가지고, 다시 고쳐졌습니다.
Gemma는 두 프로젝트 전반에 걸쳐 14개의 유령을 불러냈습니다. 제가 직접 하나하나 확인했습니다:
- 1개는 명확히 진짜입니다: 위에 있는 fish completion 관련 건입니다.
- 6개는 그럴 수도 있습니다. 같은 기능, 비슷한 증상인데 수년 간격으로 발생한 경우입니다. 마치 click의 짧은 도움말(short-help) 자르기 문제가 2014년, 2021년, 그리고 2026년에 수정된 것과 같습니다. 하지만 커밋 메시지에는 이전 수정이 실패했다고 언급하지 않아서 제가 증명할 수는 없습니다.
- 7개는 아닙니다. 같은 코드 내의 다른 버그이거나, 혹은 이전에 발생한 다른 무언가를 망가뜨린 초기 수정일 뿐입니다.
모든 판결은 HAND_CHECK.md에 있습니다.
14개 중 실제로 git 기록이 남아있는 건 단 2개였습니다: 진짜 유령, 그리고 다른 버그를 일으킨 수정입니다. 따라서 제가 신뢰할 수 있는 것은 다음과 같습니다: git은 용의자를 찾는 데 탁월하며, 그 기록(receipt)이 가장 강력한 단서이고, Gemma는 괜찮은 첫 번째 독자이지만 판단을 내리기는 서툽니다.
코드
GitHub logo connabyss / ghosts-of-bugs-past
과거 버그의 유령들 (Ghosts of bugs past)
모든 버그가 죽은 채로 머무는 것은 아닙니다. 커밋 하나가 "수정됨"이라고 말하고, 그 버그는 묻히게 되지만, 몇 달 후 또 다른 커밋이 "다시 수정하라"고 말합니다. 이 도구가 그런 것들을 파헤칩니다.
- Git이 용의자를 찾아냅니다. 어떤 수정 사항(commit)이 무언가를 고쳤다고 주장하더라도,
git blame은 그 라인을 누가 수정했는지 확인합니다. 만약 그 라인들이 이전 수정에서 온 것이라면, 이전 수정이 유효하지 않을 수 있습니다. - 로컬 오픈 모델이 판단합니다. Gemma는 (Ollama를 통해, 사용자의 자체 장치에서) 커밋 메시지와 디프(diff)를 모두 읽고, 동일한 버그가 재발하는지 여부를 판단합니다.
- 단순 검사기가 모델을 정직하게 유지시킵니다. 이 검사기는 두 커밋 메시지를 단어 그대로 인용해야 하며, 숫자를 지어낼 수 없습니다. 만약 실패하면 이유와 함께 한 번 재시도할 기회를 얻고, 그 후 답변은 폐기됩니다.
- 두 번 말해야 합니다. 판결은 "same bug came back" 또는...라는 두 구문 중 하나를 선택하는 것입니다.
저는 Claude Code의 도움을 받아 코드를 작성하고 디프를 읽으면서 이것을 만들었습니다. git과 Ollama 외에는 의존성이 없는 파이썬 파일 하나입니다:
ollama pull gemma3:12b
git clone https://github.com/pallets/click
python ghosts.py click
제작 과정: Gemma는 '예스맨'입니다 (Gemma is a yes-man)
첫 번째 버전에서는 Gemma 3 4B에게 각 용의자에 대해 간단한 질문을 했습니다: 이것이 동일한 버그인가요? 저는 이를 click에 실행했습니다.
그것은 모두 '예'라고 답했습니다.
문서(docs)만 건드린 커밋 쌍을 포함하여도 그랬습니다. 그 이유들은 매번 훌륭하게 들렸습니다: "나중의 커밋이 본질적으로 이전 커밋에서 이루어진 변경 사항을 되돌립니다..." 하지만 그것은 사실이 아니었습니다.
그래서 저는 두 번째 질문을 추가했습니다: "이것들이 다른 문제를 해결합니까?" 실제 유령이라면 '아니요'를 받아야 합니다. Gemma는 그것에 대해서도 '예'라고 말했습니다: "동일한 버그"라고 말했다면, 이제 항상 "다르다(different)"고도 말했습니다. 결과적으로 유령은 0개였습니다.
작은 모델은 질문에 상관없이 '예' 쪽으로 기울어집니다. 그래서 저는 세 가지를 변경했습니다:
- '예/아니오'는 이제 그만. Gemma가 세 가지 문구 중 하나를 선택합니다. 제가 이 문구를 추가한 이유는 그렇지 않으면 Gemma가 해당 사항들을 '같은 버그(same bug)'로 분류했기 때문입니다.
- 다른 순서로 두 번 질문하기. 두 번째 시도에서는 선택지가 역순으로 나옵니다. 무작위성(Randomness)이 꺼져 있기 때문에, Gemma가 확신하는 답변은 두 번 모두 동일하게 나옵니다. 만약 이 답변이 변경된다면, 그 답은 폐기됩니다. 이것이 Gemma를 옳다고 만들지는 않습니다. 여전히 자신만만하지만 틀린 답변은 통과할 수 있습니다. 단지 불확실한 것들만 걸러낼 뿐입니다.
- 제가 아는 답변과 대조해 보기. 저는 click 및 requests의 커밋 8쌍을 직접 읽고 레이블링했습니다: 실제 유령(real ghosts) 2개, 새로운 버그를 유발한 수정 사항 2개, 그리고 다른 문제들 4개. 그런 다음 각 설정을 점수화했습니다:
| Model | Sees the code changes (코드 변경 확인 여부) | Real ghosts found (찾아낸 실제 유령 수) | False ghosts (오탐지된 유령 수) |
|---|---|---|---|
| gemma3:4b | yes | 2 of 2 | 5 of 6 |
| ... | |||
| 여덟 쌍은 적은 양이며, 저는 이들을 보면서 프롬프트를 조정했기 때문에 이것은 벤치마크가 아닌 건전성 점검(sanity check)입니다. 심지어 12B 모델도 대부분의 경우 여전히 '같은 버그'라고 말합니다. 두 번 질문하는 것이 핵심을 포착해냅니다. |
git에서 가져온 또 다른 단순 검사들이 있습니다: Gemma는 커밋 메시지를 단어 그대로 인용해야 합니다. 만약 그렇게 할 수 없다면, 한 번의 재시도 기회를 얻고, 그 답변은 폐기됩니다. 그리고 모든 유령에는 **git 기록(git receipt)**이 표시되어야 합니다: 나중에 이루어진 커밋이 이전에 이루어진 커밋을 가리키는지(그 ID, 공유된 이슈 번호, 또는 '회귀(regression)'나 'broken' 같은 단어들)? 그 부분은 AI가 전혀 필요 없습니다.
이 모든 것은 Rohit의 TJ Holowaychuk의 생존 라인에 대한 게시물에서 시작되었습니다. 그곳에서 Gemma는 3,527개의 커밋을 '3,527년의 침묵'이라고 불렀습니다. 여기도 같은 교훈이 있습니다: 모델은 글을 잘 씁니다. 신뢰할 수 있는 것은 검증 과정입니다.
오픈 소스 AI가 중요한 이유 (Why open source AI)
당신의 git 히스토리는 팀의 사적인 일기장과 같습니다. Ollama와 Gemma를 사용하면 이 데이터는 기계를 벗어나지 않습니다: API 키도 없고, 커밋당 비용도 없으며, 누구나 python eval.py로 제 수치를 재실행할 수 있습니다.
그 정신 (The Spirit of It)
‘수정되었다가 다시 나타나는 버그’는 유령과 같습니다. 코드가 놓아주었어야 할 무언가가 여전히 복도를 배회하는 것과 같죠. 이 도구는 아무것도 퇴마하지 않습니다. 단지 묘비명만 읽을 뿐이며, 자신이 확신할 수 없을 때는 솔직하게 알려줍니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기