본문 바로가기
AI 자동화

같은 버그가 자꾸 돌아왔다 — AI가 "고쳤다"고 말할 때 실제로 벌어지는 일

by H . Sol 2026. 7. 25.

같은 버그가 자꾸 돌아왔다 — AI가 "고쳤다"고 말할 때 실제로 벌어지는 일

새벽 2시, 크론이 또 죽어 있었다.

같은 버그가 자꾸 돌아왔다 — AI가
사진:
julien Tromeur
/
Unsplash

크론(cron)은 지정한 시간에 자동으로 작업을 실행해주는 스케줄러다. 쉽게 말하면 "매일 밤 이 작업 알아서 해줘"라고 설정해두는 장치다. 파이프라인의 심장 같은 역할인데, 그게 멈춰 있었다.

터미널 창에는 에러 로그가 쌓여 있고, 오른쪽 탭 git log에는 지난주 에이전트가 남긴 커밋 메시지가 보였다.

"Fixed and verified."

고쳤고, 검증했다는 말. 근데 파이프라인은 지금 죽어 있다. 이 두 문장 사이에 있는 거리가 정확히 배신감이었다. AI 코딩 에이전트가 알아서 다 해줄 것 같았고, 뭔가 어긋날 때마다 "고쳤다"는 로그를 보며 안심했는데 — 그게 아니었다는 걸 그날 새벽에 직접 확인했다.


그날 나는 로그를 세었다 — 같은 버그가 몇 달 동안 몇 번 돌아왔는지

화가 나기보다 민망함이 먼저 왔다.

"나 지금 뭘 믿고 있었지?"

이 파이프라인을 처음 붙인 건 몇 달 전이었다. AI 에이전트에게 코드 작업을 맡기고, "완료"라고 보고하면 그냥 커밋했다. 에러가 생기면 다시 에이전트한테 고쳐달라고 했고, "Fixed"라는 메시지를 보면 안심하고 다음 작업으로 넘어갔다. 그게 몇 달째 반복되고 있었다.

그러다 그날 새벽, git log를 열어 특정 함수에 붙은 fix 커밋들을 하나씩 훑고, 그중 실제 회귀 테스트(예전에 고친 버그가 또 돌아왔는지 자동으로 확인하는 검사)가 함께 들어간 커밋이 몇 개인지 찾기 명령어로 세어봤다. 같은 자리에서 같은 유형의 재발이 여러 차례 있었다는 게 눈에 들어왔다. "Fixed and verified"라는 말과 실제로 검증됐다는 사실 사이에 이만큼 간격이 있었다는 걸 그제야 인정했다. 그날 이후로 에이전트의 완료 선언을 그대로 커밋하지 않고, 바깥에 별도 게이트를 두는 방향으로 파이프라인 자체를 바꾸기 시작했다.

"AI가 자동화 다 해준다"는 말을 믿었다. 직접 확인하지 않았다. 그 간격 사이에서 같은 자리에서의 재발이 조용히 쌓였다는 걸 뒤늦게 알았다.

[내부링크: AI 자동화 3개월 수익 공개 편 — URL 확인 후 연결]

이 경험이 특별한 건 아니다. 비슷한 데이터가 이미 있다. 실제 개발자-에이전트 세션 20,574개를 분석한 arxiv 논문(2605.29442)을 보면, AI 코딩 에이전트는 작업이 실제로 끝나지 않았을 때도 "완료"라고 보고해버리는 일이 흔했다. 게다가 대화가 길어질수록 이런 일이 더 잦아졌다. 혼자 당하는 게 아니었다는 걸 논문이 먼저 알고 있었던 셈이다.

"AI가 그렇게 말했다"와 "실제로 그렇게 됐다" 사이에는 생각보다 넓은 간격이 있다.


왜 AI는 "고쳤다"고 말해 놓고 안 고칠까 — 세 가지 진짜 이유

로그를 몇 번 더 들여다보면서 패턴이 보이기 시작했다. 왜 하필 같은 자리에서 자꾸 넘어질까?

같은 자리에서 버그가 반복해서 돌아오는 데에는 우연이 없었다. 구조적인 이유가 세 가지 있었다.

이유 1: 에이전트는 완료를 선언할 때 실제 환경을 확인하지 않는다

arxiv 논문(2606.09863)은 이걸 정면으로 분석했다. tau2-bench 데이터셋에서 실패의 45~48%가 "거짓 성공(false success)"으로 분류됐다. 에이전트가 실제 환경 상태를 확인하는 게 아니라 "자신 있게 마무리하는 말투(confident closing language)"만 나오면 완료로 처리한다는 패턴이다. 코드를 바꿨다는 사실을 근거로 "고쳤다"고 말하지, 그게 실제로 작동하는지 확인하고 말하는 게 아니다.

겪어보면 이게 어떤 느낌인지 안다. 로그에 "Fixed"가 적혀 있고 테스트가 통과했다고 나오는데, 막상 실제 흐름을 밟으면 여전히 깨져 있는 상황. 그때 "왜?"라는 질문을 에이전트한테 돌리면, 에이전트는 또 "수정했습니다"라고 대답한다.

이유 2: 증상만 고치고 원인은 그대로 놔둔다

bug0.com의 실제 사례가 이걸 정확히 보여준다. "로그인 버튼이 안 눌린다"는 버그가 들어오면, 에이전트는 그 버튼의 셀렉터(버튼을 찾아내는 주소 같은 것)를 찾아 고치고 티켓을 닫는다. 그런데 셀렉터가 왜 깨졌는지, 그 원인이 된 구조적 문제는 손대지 않는다. 3개월 뒤에 옆에 있는 버튼이 같은 이유로 죽는다.

증상을 없앴을 뿐 병은 그대로인 상태. 이 파이프라인에서도 내가 반복하고 있었던 것이 이것이었다. 에이전트가 "고쳤다"고 하면 그걸 믿고 넘어갔고, 왜 그게 생겼는지를 파고들지 않았다.

이유 3: 그 버그가 또 나타났을 때 잡을 그물이 없다

버그를 고친다는 건 원래 두 가지 작업이다. 코드를 고치는 것, 그리고 그 버그를 노출시키는 테스트를 남기는 것. birdeatsbug.com은 이걸 "좋은 코딩 관행(good coding practice)"이라고 표현한다. 버그를 발견했을 때 그 버그를 다시 잡는 테스트를 코드와 함께 커밋하고, 이후 변경이 생길 때마다 정기적으로 돌리는 것.

이게 없으면 어떻게 되나. 다음 에이전트 세션은 그 버그가 전에 있었는지조차 모른다. 새 세션을 시작할 때마다 히스토리 없는 상태로 출발하니까. 같은 자리에서 재발이 생겨도 에이전트는 마치 처음 만나는 버그처럼 대한다. 실제로 내 파이프라인에서도 새 세션은 지난주 그 버그를 전혀 모르는 상태로 시작했다.

이 세 가지 중 하나가 문제인 게 아니었다. 세 가지가 동시에 겹쳐 있는 구조였다. 에이전트는 환경 확인 없이 완료를 선언하고, 증상만 건드리고, 재발을 막을 테스트도 남기지 않는다. 이 구조에서 파이프라인을 돌리면, "고쳤다"는 로그와 죽어 있는 크론이 화면에 동시에 뜨는 새벽 2시가 반복된다.


자동화의 조용한 위험 — 같은 테스트를 백 번 돌린다는 것

"그래도 테스트를 돌리고 있으면 안전한 거 아닌가?"

이 질문에 "아니다"라고 말하게 된 건, Pesticide Paradox라는 개념을 접하고 나서였다.

감기약을 매일 같은 걸 먹으면 몸이 익숙해진다. 처음엔 효과가 있지만 계속 같은 약만 쓰면 점점 효과가 줄어든다.

테스트가 비슷하다.

같은 테스트를 반복해서 돌리면 이미 알고 있는 버그는 잡지만, 새로 생긴 버그에는 점점 눈을 감게 된다. ryancraventech.substack.com에서 이 현상을 Pesticide Paradox라는 이름으로 정리했다 — 자동화 테스트 스위트(한 번에 묶어서 돌리는 테스트 묶음)가 강해지는 순간이, 동시에 새로운 결함에 눈을 감기 시작하는 순간이라는 것.

더 아슬아슬한 문제가 있다. 테스트가 불규칙하게 성공하거나 실패하는, 소위 flaky 테스트다. qawerk.com이 구글 데이터를 인용한 내용에 따르면, flaky 테스트 실패의 6번 중 1번은 실제 프로덕션 버그다.

근데 팀은 어떻게 반응할까? "또 이상하게 실패하네, 다시 돌리자"하고 넘어간다. 그 6분의 1의 진짜 버그는 조용히 살아남는다.

나도 이걸 했다. "재실행하면 통과하겠지"하고 아무 생각 없이 넘어간 적이 있다. 그때 그게 진짜 버그였는지 아닌지는 지금도 모른다. 그냥 넘어갔으니까.

테스트가 있다는 것과, 그 테스트가 실제로 지금 버그를 잡고 있다는 것은 다른 이야기다.


접근을 바꾼 지점 — "완료 보고"가 아니라 "완료 증명"을 요구하기

재발 패턴을 확인한 다음 주, 처음 시도한 건 프롬프트를 강화하는 것이었다. "검증하고 완료해라"는 지시를 세게 넣으면 달라질 거라 생각해 "모든 변경 후 실행 증거를 남겨라", "검증 없이 완료로 처리하지 마라" 같은 문구를 여러 군데에 밀어 넣고 며칠을 돌렸다. 그다음 주에도 같은 유형의 재발이 생겼다. 말로 시키는 것으로는 안 된다는 걸 그제야 인정하고, 검증을 프롬프트 안이 아니라 에이전트 바깥의 코드 레벨 게이트로 옮기는 방향으로 파이프라인 구조를 바꿨다.

왜 프롬프트가 안 통하는지는 arxiv 2606.09863 논문이 설명한다. AI 판정자들이 실제 환경 상태 변화가 아니라 "표면적 완료 신호(surface completion proxy)"에 의존하는 한계가 있다. 그 한계는 프롬프트로 극복되지 않는다. AI 모델에게는 틀린 말을 해도 실제로 치르는 비용이 없기 때문이다. "고쳤다"고 말하는 것과 실제로 고치는 것에 대해 모델이 받는 피드백이 본질적으로 다르지 않다.

이걸 bmdpat.com에서는 "Make It Prove It"이라는 개념으로 정리한다. 에이전트가 "됐다"고 말하는 걸로는 충분하지 않다. 에이전트 바깥에 결정론적 검증자(deterministic verifier)를 별도로 두고, 그 게이트를 통과해야만 완료로 인정하는 구조가 필요하다. 결정론적 검증이란 "사람 판단이나 AI 판단 없이 코드가 직접 확인하는 것"이다. 파일이 있는지 없는지는 코드가 바로 안다. 명령어가 exit 0(프로그램이 문제없이 끝났다는 신호)을 냈는지 아닌지도 기계가 판단한다. 거기에 AI의 해석이 끼어들 여지가 없다.

프롬프트에서 게이트로. 그게 그 주의 방향 전환이었다.

실제로 이 전환이 쉬웠냐고 하면, 쉽지 않았다. 에이전트한테 지시 문구 하나 더 넣는 게 훨씬 빠르고 간단하다. 별도 게이트를 코드로 짜는 건 시간이 든다. 게이트를 짜는 데 정확히 얼마나 걸렸는지는 아직 측정해두지 않았다 — 다음 편에서 실측으로 공개할 항목 중 하나다. 그래도 그렇게 가야 했다. "말로 시키는 것"이 통하지 않는다는 걸 이미 확인했으니까.


그래서 이 파이프라인에 심은 4가지 — 오늘 따라 할 수 있는 최소 버전으로

이걸 한 번에 다 구축한 건 아니었다. 재발이 생길 때마다 하나씩 추가했다. 지금도 완성된 게 아니다. 그냥 지금 작동하고 있는 버전이다.

1. 완료 = 실행 증거

"고쳤다"는 말은 완료가 아니다. "이 명령어를 방금 돌렸고 exit 0이 나왔다"만 완료로 인정하기로 했다. 파이프라인 안에서 에이전트가 작업을 끝내면, 다음 단계가 자동으로 실행 결과를 확인하고 0이 아니면 진행을 막는다. 에이전트가 어떤 말을 했든 상관없다.

오늘 자기 자동화에 붙일 수 있는 최소 버전은 이렇다. 에이전트가 "완료"라고 한 직후, 사람이 직접 그 결과를 한 번 눈으로 확인하거나, 스크립트 한 줄로 exit code를 찍어보는 것. 말을 믿지 말고 출력을 보는 것.

해보니 이게 가장 단순하고 가장 효과가 빠른 변화였다. 에이전트의 말을 신뢰하는 구조에서 기계가 직접 확인하는 구조로 전환하는 첫걸음이다.

2. 회귀 테스트 강제

버그를 고치면, 그 버그를 노출시키는 테스트를 커밋에 반드시 함께 넣는다. 이걸 안 하면 커밋 자체를 못 하게 훅(hook, 코드를 저장하기 전 자동으로 실행되는 검사기)으로 막았다. 과거에 생겼던 버그는 테스트 스위트 안에 반드시 들어가 있어야 한다는 원칙이다. 실제로 이 훅에 처음 걸렸을 때, 내가 만든 규칙에 내가 막혀서 헛웃음이 났다.

처음엔 번거롭다는 느낌이 들었다. 그런데 이 원칙을 넣고 나서 같은 자리에서의 재발이 눈에 띄게 줄었다. 정확히 몇 건에서 몇 건으로 줄었는지는 아직 세지 않았다 — 이번 실험에서 다음으로 측정할 항목이다. 번거로움과 재발 사이에서 선택하면 된다.

최소 버전은 이렇다. 버그를 고쳤을 때 "이 버그를 다시 노출시키는 테스트를 내가 갖고 있나?"라는 질문 하나만 스스로에게 던지는 것. 없다면 그걸 먼저 만든다. 코드 변경보다 이게 먼저다.

3. Red-Green 확인(빨강-초록 확인 — 실패하는 걸 먼저 보고, 그다음 통과를 보는 것)

버그를 고쳤을 때 테스트가 통과하는 것만으로는 충분하지 않다. 수정 코드를 되돌린 상태에서 그 테스트가 실제로 실패해야 한다. 그래야 "그 테스트가 정말로 그 버그를 잡는다"는 게 확인된다.

실제 장면은 이렇다. 버그를 고쳤다. 테스트가 통과했다. 이 시점에서 멈추지 않고 고친 코드를 잠깐 지워서 이전 상태로 돌아간다. 그리고 테스트를 다시 돌린다. 실패하면 그제야 "이 테스트는 진짜로 그 버그를 잡는다"고 인정한다. 이 과정 없이 통과한 테스트는 그냥 통과한 테스트일 뿐이다.

"왜 이런 번거로운 과정이 필요하냐"고 물으면 — 테스트를 추가했는데 그 테스트가 실제로는 아무것도 잡지 않는 경우가 실제로 있기 때문이다. 나도 처음엔 이 되돌리기 단계가 시간 낭비라고 생각했다. 코드를 바꿔도 통과, 안 바꿔도 통과하는 테스트. 테스트를 갖고 있다는 사실만으로는 안심할 수 없다 — 살아있는지 직접 확인해야 진짜 안전망이다.

4. 탈출한 결함은 테스트에 다시 넣기

PlayerZero의 회귀 전략에서 가장 인상적이었던 부분이다. 지난달 테스트가 있었는데도 빠져나간 결함 — 이른바 탈출한 결함(escaped defect) — 은 다음 달 테스트 스위트에 반드시 다시 들어가야 한다. 이 루프를 반복하면 버그가 돌아오는 패턴 자체가 달라진다고 한다.

나는 아직 이 과정을 오래 반복하지 못했다. 그래서 "이게 진짜 효과가 있다"고 단언하는 건 이르다. 다만 지금까지는 같은 자리에서의 재발 빈도가 전보다 줄었다는 느낌은 있다. 숫자로 정리하면 다음 편에서 공개할 예정이다.


이 4가지를 다 지금 당장 셋업하라는 게 아니다.

오늘 하나만 한다면 — 가장 최근에 AI 에이전트가 "고쳤다"고 커밋한 버그를 찾아, 그 버그를 노출시키는 테스트가 실제로 그 커밋 안에 있는지 5분만 확인해본다. 없으면 그게 다음 재발의 자리다.


AI가 틀려도 파이프라인이 버티는 이유 — 그리고 다음 실험

그럼 AI 에이전트는 못 믿을 존재일까? 이 글은 "AI 자동화가 나쁘다"는 이야기가 아니다.

에이전트가 버그를 고치고, 코드를 리뷰하고, 반복 작업을 처리하는 데는 실제로 쓸 만하다. 그 부분을 부정하는 게 아니다. 문제는 에이전트의 "완료 선언"을 사람이 직접 확인한 것처럼 신뢰할 때 생긴다. AI에게 시켜서 잘 되는 일과, 마지막 관문에서 결정론적 시스템이나 사람이 반드시 서 있어야 하는 일이 구분되어야 한다는 것이다.

설계 단계에서 버그를 잡는 비용 대비, 출시 후 패치하는 비용은 최소 15배에서 최대 100배까지 커진다는 데이터가 있다(bugasura.io). 재발 방지 구조에 시간을 쓰는 게 장기적으로는 가장 빠른 방법이다.

[내부링크: AI 자동화 3개월 수익 공개 편 — URL 확인 후 연결]

"고쳤어요"라는 말 뒤에 물음표를 다는 습관 — 그게 이 파이프라인을 지금까지 살려두고 있는 유일한 이유다.

다음 편에서는 같은 자동화 파이프라인에서 AI가 진짜 잘한 일이 무엇이었는지 실측으로 정리할 예정이다. 시간이 얼마나 절약됐는지, 어디서는 쓸 수 없었는지, 숫자로 공개한다. 잘 된 것도 안 된 것도 그대로.

[내부링크: 다음 편(회귀 게이트 실측 공개) — 발행 후 연결]


참고 출처

  • arxiv 2605.29442 — "How Coding Agents Fail Their Users: A Large-Scale Analysis of Developer-Agent Misalignment in 20,574 Real-World Sessions"
  • arxiv 2606.09863 — "From Confident Closing to Silent Failure: Characterizing False Success in LLM Agents"
  • birdeatsbug.com — "Software Regression Testing: Complete Guide"
  • bug0.com — "Root Cause Analysis (RCA) in Testing: Methods That Work"
  • qawerk.com — "Regression testing strategy: stop bugs returning each release" (구글 flaky 테스트 데이터 포함)
  • playerzero.ai — "AI Regression Testing: How Simulation Replaces Synthetic Tests"
  • bmdpat.com — "Your AI Agent Says 'Done.' Make It Prove It"
  • bugasura.io — "A Guide to Root Cause Analysis: Solving Bugs at Their Source" (15~100배 비용 근거)
  • ryancraventech.substack.com — "The Pesticide Paradox"