
AI가 작성한 15개의 자동화 스크립트, 모두 같은 곳에서 버그가 발생했다 — 다른 AI에게 감사를 맡겨서 알게 된 사실
요약
Claude Code로 작성한 15개의 자동화 스크립트에서 공통적인 버그 패턴을 발견한 사례를 공유합니다. 동일한 모델이 작성하고 리뷰할 때 발생하는 사각지대를 지적하며, 타 모델(GPT, Gemini)을 통한 교차 검증의 중요성을 강조합니다.
핵심 포인트
- AI 모델은 Happy Path 위주의 코드를 작성하는 경향이 있음
- 동일 모델이 작성과 리뷰를 모두 수행할 경우 사각지대가 반복됨
- Race condition, Fail-open, Error swallowing 등 3가지 공통 버그 발견
- 교차 모델(GPT, Gemini)을 활용한 코드 감사가 필수적임
20세 대학생입니다. Claude Code에게 회사(같은 무언가)를 운영하게 하며, 메일 모니터링·자동 답장 초안 작성·캘린더 등록·수익 집계 등 외부로 사이드 이펙트(side effect)를 가지는 자동화 스크립트(sender라고 부릅니다)를 15개 정도 운용하고 있습니다.
얼마 전, ChatGPT Plus를 결제한 것을 계기로 Codex CLI(GPT 계열)와 Gemini CLI에게 Claude가 작성한 15개를 전부 감사(audit)하게 해보았는데, 상상보다 훨씬 흥미로운 결과가 나와서 공유합니다.
결과: 15개 중 15개가 「같은 3곳」에서 버그가 있었다
개별적인 버그가 흩어져 있었던 것이 아닙니다. 전부 같은 3가지 패턴이었습니다.
① non-atomic한 read-modify-write (check-then-write race)
// 흔한 형태 (15개 중 9개가 이것)
const state = JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'));
if (!state[key]) {
...
단독으로는 올바르게 보입니다. 하지만 cron의 중복 실행이나 watchdog의 재시동으로 2개의 프로세스가 동시에 실행되면, 양쪽 모두 「미전송」을 읽고 양쪽 모두 전송해 버립니다. 실제로 저희 쪽에서는 과거에 이 형태가 원인이 되어 동일한 상대에게 초대 메일이 4중 전송되는 사고가 발생한 적이 있습니다.
② 파싱(parse) 실패 시의 fail-open
function loadState() {
try { return JSON.parse(fs.readFileSync(STATE_FILE, 'utf8')); }
catch { return {}; } // ← 손상 = 빈 객체 = 「전부 미처리」 취급
...}
state 파일이 깨졌을 때 빈 객체를 반환하면, 「전건 처리 완료」가 「전건 미처리」로 변하여 전부 재전송됩니다. 최초 실행(파일 부재)과 손상을 구분하여, 손상 시에는 throw로 중단(fail-closed)했어야 했습니다.
③ 사이드 이펙트 실패를 무시함 (swallowing errors)
try { await sendLineMessage(msg); } catch (_) {}
알림이나 전송 실패를 무시하면, 호출 측은 성공했다고 생각하고 상태를 확정합니다. 재전송도 되지 않고, 실패가 관측조차 되지 않습니다.
왜 「전부 같다」는 것인가 — correlated blind spot
이 부분이 가장 큰 배움이었습니다. 15개의 스크립트는 수개월에 걸쳐 서로 다른 문맥에서 작성된 코드입니다. 그런데 그것이 같은 곳에서 버그를 일으킵니다.
즉, 이것은 개별적인 실수가 아니라 **모델의 「쓰기 습관」**입니다. Claude는 happy path 로직은 깔끔하게 작성하지만, 「병행 프로세스가 동시에 실행된다면」, 「state가 손상되었다면」, 「알림이 끊긴다면」과 같은 상황을 확률적으로 동일한 우선순위로 생략합니다.
그리고 중요한 점은, 이 15개를 수개월 동안 리뷰해 온 것도 Claude 자신이었다는 사실입니다. 작성한 본인과 동일한 사각지대를 가진 리뷰어는 같은 곳을 그냥 지나칩니다 (self-preference bias / correlated failure). GPT와 Gemini라는 별개의 계통 모델에게 보여준 순간, 첫 감사에서 전부 드러났습니다.
흥미로웠던 점은 심각도(severity) 판정까지 갈렸다는 것입니다. 동일한 이중 실행 버그에 대해 Codex는 BLOCKING, Gemini는 SHOULD-FIX라고 판정했습니다. 독립된 눈은 틀리는 방식까지 독립적입니다.
수정 방법: advisory lock + 병행 프로세스 테스트
처방전 자체는 고전적입니다. 외부 의존성이 없는 file lock (fs.openSync의 wx 플래그 = atomic create)으로 mutex를 만들고, check와 write를 lock 내부에 넣습니다.
// advisory_lock.js (개요)
function acquire(target) {
const lockPath = target + '.lock';
...
예약 패턴(reserve → 사이드 이펙트 → confirm / 실패 시 revoke)으로 변경하고, 12개의 프로세스가 동시에 reserve를 호출했을 때 승자가 정확히 1개가 되는지를 실제 테스트했습니다. 수정 전에는 평범하게 2~3개의 프로세스가 「승리」하고 있었습니다.
race test: 12 병렬 reserve → WON=1 ✅ / 15 병렬 RMW → lost-update 0 ✅
요약: AI에게 코드를 작성하게 한다면
AI가 작성한 코드를 동일한 AI에게 리뷰하게 하지 마세요. 다른 계열의 모델(GPT ⇄ Claude ⇄ Gemini)을 하나만 끼워 넣어도, 첫 시도에서 계통적 맹점(systemic blind spot)이 드러납니다. -
외부 부작용(external side effect)을 가진 코드는 반드시 **"두 개가 병렬로 실행된다면"**이라고 물어보세요. AI는 묻지 않는 한 이것을 작성하지 않습니다. -
catch { return {}; }와 catch (_) {}를 grep으로 검색하는 것만으로도, 상당히 높은 확률로 시한폭탄을 찾아낼 수 있습니다.
평소의 운영 기록은 AI Hack Lab에 작성하고 있습니다. Claude Code로 회사를 운영하는 실록은 이쪽에서 확인하세요.
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기