에이전트가 한 번의 행(hang)을 해결하자마자 즉시 다른 행을 만들어냈다
요약
AI 에이전트가 코드의 결함을 수정하는 과정에서 새로운 결함(hang 상태)을 유발하는 사례를 분석합니다. 에이전트가 작성한 수정 코드가 기존 리뷰를 무력화하지 않도록, 수정된 코드 전체를 대상으로 하는 새로운 검토 프로세스의 필요성을 강조합니다.
핵심 포인트
- 에이전트의 수정 사항이 새로운 논리적 결함이나 행(hang) 상태를 유발할 수 있음
- 수정된 코드가 기존의 검토(Review) 결과를 무효화하는 컨텍스트 드리프트 문제 발생
- 에이전트 검토 프로세스에도 GitHub의 승인 취소 방식과 같은 즉각적인 재검토 규칙이 필요함
Mindrealm도 두 번째 사례를 잡아냈습니다. 에이전트가 작성한 기능에서 발견된 21개의 결과 중 19개가 실제 문제였습니다.
Mindrealm은 영원히 행(hang) 상태에 빠질 수 있는 헬스 체크(health check)를 찾아냈습니다. 타임아웃(timeout) 없이 기본 HTTP 클라이언트를 사용했기 때문에, 만약 서버가 연결은 수락했지만 응답을 보내지 않는다면 무언가가 이를 종료할 때까지 계속 대기하게 됩니다.
에이전트는 약 1분 만에 이를 수정했습니다. 클라이언트에 10초의 타임아웃을 설정했고, 타임아웃이 적용되었음을 증명하는 테스트를 작성했습니다.
에이전트는 아직 깨닫지 못했지만, 방금 작성한 그 새로운 테스트 역시 CI에서도 영원히 행(hang) 상태에 빠질 수 있었습니다.
테스트 내부에서 핸들러(handler)가 타임아웃 없이 빈 채널 수신(bare channel receive)에서 블로킹(blocking)되었습니다. 만약 테스트가 조기에 반환되면, 테스트가 시작한 서버는 결코 오지 않을 값을 기다리며 종료(shutdown) 단계에서 대기하게 됩니다. 실패하는 테스트 세트보다 행(hang) 상태에 빠진 테스트 세트가 더 나쁩니다. 적어도 실패하는 테스트는 무엇이 고장 났는지 즉시 알려주기 때문입니다. 멈춰버린 프로세스는 전체 작업의 타임아웃(job timeout)을 다 써버리게 만들고, 그러면 누군가는 왜 그런 일이 발생했는지 알아내기 위해 덤프(dump)를 뒤져야 합니다.
이후 Mindrealm의 검사에서 이 문제가 포착되었습니다. 그리고 에이전트는 마땅히 해야 했듯이 수신(receive) 부분을 타임아웃이 포함된 select 문으로 감쌌습니다.
같은 에이전트, 같은 파일, 불과 몇 분 차이였습니다. 누락된 타임아웃을 해결하기 위한 수정 사항이 새로운 타임아웃 누락을 초래했고, 아이러니하게도 그 수정 사항은 첫 번째 문제가 사라졌음을 증명하는 것이 목적이었던 바로 그 테스트 코드에 포함되었습니다.
새로운 코드가 기존 리뷰를 구식으로 만든다
저의 자동화된 코드 리뷰(code review) 프로세스는 간단합니다. 결과(findings)가 나오고, 에이전트가 이를 처리하여 개수가 0이 되면 완료됩니다.
하지만 이 모델에는 허점이 있습니다. 수정 사항들은 원래 리뷰를 받았던 코드의 일부가 아닙니다. 그것들은 결함을 작성했던 바로 그 에이전트가 빠르게 작성한 새로운 코드이며, 종종 컨텍스트 팽창(context bloat)이 이미 컨텍스트 부패(context rot)와 드리프트(drift)를 유발한 이후에 작성됩니다. 그 어떤 것도 새로운 코드를 살펴보지 않았으며, 그것은 아마도 가장 허술한 코드일 수도 있습니다.
사람의 검토(Human review)는 이미 이 문제를 해결했습니다. GitHub는 새로운 커밋이 diff를 변경할 때 오래된 승인을 취소(drop a stale approval when new commits change the diff)할 수 있도록 허용합니다. 누군가 승인한 브랜치에 푸시(Push)하면 그 승인은 더 이상 유효하지 않게 되는데, 이는 더 이상 일치하지 않는 코드를 승인했던 것이기 때문입니다.
에이전트 검토(Agent review)도 동일한 규칙이 필요하며, 루프(loop)가 사람이 검토하는 속도보다 훨씬 빠르게 돌아가기 때문에 더 즉각적으로 적용되어야 합니다. 수정 사항을 만드는 동안, 에이전트는 발견(finding)을 유발한 해당 라인 이상의 코드를 변경할 수 있습니다.
검토는 교체된 버전이 아니라, 수정을 거친 후의 새로운 코드를 따라가며 판단해야 합니다. 이는 수정 커밋에 대해 CI를 다시 실행하도록 하는 것과는 다릅니다. 당신의 린터(linter)나 테스트 실행은 타임아웃(timeout)이 없는 채널 수신(channel receive)을 결코 잡아내지 못했을 것입니다. 그것은 코드 검토(code review)를 통해 발견된 버그이며, 저의 검토는 첫 번째 패스(pass) 이후에 이를 차단했습니다.
동일한 실행 과정에서 두 개의 버그가 더 나타났습니다. 에이전트들이 다른 무언가를 수정하는 동안 연결 래퍼(connection wrapper)를 추가했는데, 이것이 어떤 작업이 실패했는지 명시하지 않은 채 데드라인 에러(deadline errors)를 반환했습니다. 그래서 다운스트림(downstream)의 실패는 무언가 타임아웃되었다는 것은 알려주었지만, 무엇인지는 알려주지 못했습니다. 나중에 Mindrealm 패스(passes)가 두 이슈를 모두 포착했고, 에이전트는 누락된 에러 컨텍스트(error context)를 추가했습니다.
그리고 동일한 구간에서 나온 네 번째 발견 사항은 잘못된 것이었습니다. 즉, 거짓 양성(false positive)이었으며, 제가 이를 어떻게 처리했는지 설명하겠습니다.
에이전트의 수정 사항이 네 개의 발견 사항을 더 생성했다
이틀 동안, AI 코딩 에이전트들은 Claude Code 샌드박스(sandbox)가 연결할 수 있는 위치를 제한하는 네트워크 프록시(network proxy)를 구축했습니다. Mindrealm은 검토에 의해 유도된 수정 사항을 포함하여 개발 전 과정 동안 코드를 검토했습니다. 그 기간 동안 21개의 고유한 발견 사항(unique findings)을 보고했습니다. 21개 중 19개는 실제였고 2개는 잘못되었으며, 이는 9.5%의 거짓 발견율(false discovery rate)을 나타냅니다.
이 수치는 AI 에이전트에 의한 단일 기능 개발 결과이며, 제품의 전반적인 거짓 발견율이 아니라는 점을 분명히 하고 싶습니다. 마지막 섹션에서 왜 이 차이가 중요한지 설명하겠습니다.
그 21개의 발견 사항 중 4개는 에이전트가 이전 발견 사항들을 수정하는 동안 작성한 코드에 포함되어 있었습니다. 그중 3개는 실제 버그였습니다. 만약 리뷰가 첫 번째 배치(batch) 이후에 멈췄다면, 그 3개의 버그는 그대로 배포되었을 것입니다. 그중 하나는 CI(지속적 통합)를 중단(hang)시킬 수 있는 테스트였습니다. 에이전트는 19개의 실제 버그에 대한 코드를 모두 수정했습니다. 잘못된 2개의 경우, 에이전트는 해당 파일의 각 라인에 특정 오탐(false positive)을 억제(suppressing)해야 하는 이유를 기록했습니다.
Mindrealm은 최종 패스(pass)에서 다음과 같이 보고할 때까지 변경 사항을 계속해서 리뷰했습니다:
발견된 문제 없음 (66개 억제됨).
최종 패스에서는 억제되지 않은 발견 사항이 0건이었습니다. 또한 66건의 억제 사항이 공개되었습니다. 62건은 새로운 기능이 추가되기 전부터 저장소(repository)에 이미 존재하던 것이었습니다. 저는 이 결과를 더 깨끗한 '0'으로 바꾸지 않고, 두 숫자를 모두 보고하고 있습니다. 저의 목표는 저 자신의 억제 사항 또한 0으로 만드는 것입니다.
저는 잘못된 보고를 생성하는 각 체크(check)를 수정하기 위한 태스크(task)를 생성했습니다. 에이전트가 이를 올바르게 거부하더라도, 오탐(false positive) 발견은 코드 리뷰 규칙의 결함입니다. 왜냐하면 모든 잘못된 보고는 시간을 낭비하며, 엔지니어와 에이전트가 시스템을 우회하도록 가르치기 때문입니다.
코드 리뷰를 신뢰하기 위해 내가 이제 원하는 다섯 가지
만약 에이전트가 작성한 코드에 대해 차단형 리뷰(blocking review)를 실행하고 있다면, 제가 요구하는 시퀀스(sequence)는 다음과 같습니다.
-
고유한 발견 사항(Unique findings) 및 해당 코드가 어떤 종류였는지. 스캔 전반에 걸쳐 중복을 제거(Deduplicated)해야 하며, 무엇을 검토했는지 구체적이어야 합니다. 에이전트가 작성한 최신 코드와 10년 된 라이브러리는 서로 다른 숫자를 가진 다른 문제입니다.
-
모든 발견 사항에 대해 저장된 결과(An outcome). 코드가 수정되었는지, 서면 사유와 함께 발견 사항이 거부되었는지, 혹은 이슈가 여전히 열려 있는지(open)를 기록해야 합니다.
-
각 수정 사항에 대한 재현 가능한 증거(Reproducible proof). 에이전트가 처리했다는 보고서가 아니라, 버그가 존재하기 때문에 빨간색(red)으로 시작했다가 수정되었을 때 초록색(green)으로 변하는 집중된 테스트가 필요합니다.
-
변경 된 코드에 대한 새로운 검토(A fresh review). 테스트 커버리지(test coverage)와 마찬가지로 이는 많은 사람들이 건너뛰는 단계이며, 제가 발견한 21가지 사례 중 3가지가 바로 이 단계에서 나왔습니다.
-
결과와 함께 공개된 억제(Suppressions) 및 미결 발견 사항(open findings). 제외 항목을 숨긴 깨끗한 결과는 정확한 묘사가 아닙니다.
이러한 요소들이 없다면, 발견 사항의 개수는 리뷰어가 얼마나 소음(noisy)을 일으켰는지를 주로 알려줄 뿐입니다.
발견 숫자는 검토된 코드의 성숙도에 따라 달라집니다
이 기능은 9.5%의 허위 발견율(false discovery rate)로 출시되었습니다. 저는 여러분이 직접 자신의 저장소(repository)에서 확인하게 두느니, 왜 이것이 제품의 보증 사항이 아닌지를 설명하는 쪽을 택하겠습니다.
성숙한 오픈 소스 저장소에 대한 초기 실행 결과는 훨씬 더 나빴습니다. 저는 에이전트에게 인기 있고, 유지 관리되며, 잘 작성된 오픈 소스 저장소의 발견 사항 샘플을 분류하도록 했습니다. Kubernetes의 경우 분류된 1,647개의 발견 사항 중 추정 허위 발견율이 65.1%로 나타났습니다. Prisma는 62.6%로, 제가 샘플링한 1,199개의 발견 사항 중 750개가 잘못된 보고였습니다. Temporal의 Go SDK는 76.2%로, 731개 중 557개가 잘못된 것이었습니다. 해당 실행은 허위 발견율을 개선하기 위한 이후의 정밀 작업(precision work) 이전의 것이므로, 현재의 비율은 아니며 이번 실행과 직접적인 비교 대상은 아닙니다. 하지만 이 결과가 보여주는 것은 코드의 품질과 성숙도가 숫자를 수십 퍼센트 단위로 변화시킨다는 점이며, 따라서 대상 코드를 명시하지 않고 인용된 정밀도 수치는 아무런 정보도 제공하지 못한다는 것입니다.
에이전트가 작성한 코드에 대한 저의 작업 범위(working range)는 약 10%에서 20% 사이입니다. 저의 목표는 모든 종류의 코드에 대해 이 수치를 10% 미만으로 낮추는 것이지만, 아직 그 단계에 도달하지는 못했습니다. 에이전트가 생성한 새로운 코드(Fresh code)는 현재 Mindrealm이 잘 수행하고 있는 영역입니다. 반면, 성숙하고 활발하게 유지 관리되는 저장소(repositories)는 여전히 정밀한 작업이 필요한 영역으로 남아 있습니다.
Mindrealm의 스톱 훅(stop hook)은 Claude Code, Codex, 그리고 Gemini에서 작동합니다. 에이전트가 코드를 작성하면 Mindrealm이 검토하고, 에이전트가 수정 가능한 부분을 수정하면 다시 Mindrealm이 변경 사항을 검토합니다. 마지막 검토 단계는 단순히 형식적인 절차가 아닙니다. 이 기능을 통해 세 개의 실제 결함(defects)이 발견되었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기