Instinctools가 최근 ‘Vibe Code Audit and Cleanup’이라는 서비스를 냈다는 소식을 보고, 이제 AI 생성 코드도 “만들기”보다 “감사하고 정리하는 단계”가 별도 시장으로 분리되는구나 싶었습니다. 특히 Claude Code식 실행형 에이전트나 자연어 자동화 빌더를 쓰다 보면, 코드가 돌아가는 것처럼 보이는 순간 바로 배포하고 싶은데 여기서 사고가 자주 납니다.
제가 배포 전 체크할 때 제일 먼저 보는 건 코드 스타일이나 주석이 아니라 AI가 만든 기본값이 운영 환경에서 위험한 값으로 굳어졌는지입니다. AI 코드는 의외로 “임시로 넣은 편의 옵션”을 끝까지 유지하는 경우가 많습니다. 예를 들면 `CORS: *`, `debug: true`, `timeout: 0`, `verify=False`, `NODE_ENV` 미설정, `rateLimit` 없음 같은 값입니다.

실무에서 가장 많이 본 함정은 “성공 응답 기준”입니다. AI가 API 연동 코드를 만들 때 `response.ok` 또는 `status === 200`만 보고 성공 처리하는 경우가 많은데, 실제 자동화 API는 202, 204, 207, 429를 섞어 씁니다. 특히 202는 “접수됨”이지 “완료됨”이 아니라서, 이걸 완료로 처리하면 뒤쪽 워크플로가 빈 데이터로 계속 진행됩니다.
그래서 저는 AI가 만든 코드 배포 전에는 테스트를 기능 단위로 보지 않고 “상태 코드 테이블”부터 만듭니다. 최소한 200, 201, 202, 204, 400, 401, 403, 404, 409, 422, 429, 500, 502, 503을 넣고, 각 상태에서 재시도 여부와 사용자 표시 메시지를 분리합니다. 이걸 안 하면 나중에 장애 대응할 때 “왜 실패했는지”가 아니라 “실패인지도 몰랐는지”부터 추적하게 됩니다.
재시도 로직도 AI 코드에서 자주 과하게 들어갑니다. 특히 자동화 코드에서는 `maxRetries: 3` 정도가 무난해 보이지만, 외부 API가 429를 주는 상황에서 워커 10개가 동시에 3회 재시도하면 순식간에 40회 호출이 됩니다. 저는 기본값을 `maxRetries: 2`, `initialDelayMs: 500`, `backoffFactor: 2`, `jitter: true`로 두고, 400/401/403/404/422는 재시도 금지로 고정합니다.

보안 쪽에서는 시크릿 노출보다 더 은근한 문제가 있습니다. AI가 `.env.example`을 만들면서 실제 키 형식을 너무 자세히 남기는 경우입니다. 예를 들어 `sk_live_...`처럼 prefix를 그대로 적어 두면 내부 저장소 검색이나 로그 필터링에서 오탐·누락이 생깁니다. 샘플은 `PAYMENT_API_KEY=replace_me`처럼 서비스 식별이 안 되는 값으로 바꾸고, 실제 키 prefix가 필요한 검증은 코드가 아니라 런타임 설정 문서에만 둡니다.
의존성도 단순히 최신 버전인지보다 “AI가 존재하지 않는 패키지명을 섞었는지”를 봐야 합니다. 가끔 비슷한 이름의 패키지를 설치하게 만들거나, 문서에서 본 이름과 npm/pip 실제 패키지명이 다른 경우가 있습니다. Node라면 `npm ls --depth=0`으로 직접 설치된 목록을 확인하고, Python이면 `pip freeze`보다 `pipdeptree --warn fail`을 한 번 돌리는 쪽이 충돌을 빨리 잡습니다.
DB 마이그레이션은 AI 코드에서 가장 조심하는 부분입니다. `nullable: false` 컬럼을 기존 테이블에 바로 추가하는 패턴이 자주 나오는데, 로컬 빈 DB에서는 통과하고 운영 DB에서는 터집니다. 저는 AI가 만든 마이그레이션에 대해 “추가, 채움, 제약”을 한 파일에 넣지 못하게 합니다. 새 컬럼 추가는 nullable로 먼저, 백필 스크립트 별도, 마지막에 not null 제약을 거는 식으로 나눕니다.
로그는 많을수록 좋은 게 아니라, 재현 가능한 식별자가 있어야 합니다. AI가 `console.log(error)`만 넣어 둔 코드는 장애 때 거의 도움이 안 됩니다. 요청 단위 `requestId`, 외부 API 호출 단위 `providerRequestId`, 내부 작업 단위 `jobId`를 분리해서 남겨야 어느 구간에서 꼬였는지 바로 갈립니다.
개인적으로 배포 전 감사 체크리스트에 꼭 넣는 항목은 “삭제·수정 계열 액션의 dry-run 지원 여부”입니다. AI 에이전트가 만든 자동화 코드는 생성보다 정리, 이동, 삭제 작업에서 사고가 큽니다. CLI든 배치든 기본 실행은 `--dry-run`으로 결과 건수와 대상 ID만 출력하게 하고, 실제 실행은 `--apply` 옵션이 있어야만 진행되게 만듭니다.
예를 들면 이런 식으로 플래그를 강제합니다.
node cleanup.js --dry-run
node cleanup.js --apply --confirm "DELETE_OLD_ITEMS"여기서 `--apply`만으로 실행되게 하면 실수로 눌릴 수 있어서, 저는 파괴적 작업에는 확인 문자열까지 요구합니다. 너무 번거로워 보여도 운영에서 한 번 사고 막으면 충분히 값어치가 있습니다.

프론트 코드에서는 AI가 상태 관리를 낙관적으로 처리하는지 확인합니다. 저장 버튼을 누르자마자 UI를 성공 상태로 바꾸고, 실패하면 롤백하는 방식 자체는 괜찮습니다. 문제는 실패 시 원본 값을 어디에 보관하는지 없이 `setState`만 여러 번 호출하는 코드입니다. 이 경우 빠르게 두 번 클릭하거나 네트워크가 느릴 때 화면과 서버 값이 어긋납니다.
자동화 워크플로 코드라면 “중복 실행 방지 키”가 있는지도 봅니다. 같은 입력이 두 번 들어왔을 때 결과가 두 번 생성되면 안 되는 작업이 많습니다. 저는 보통 `sourceType + sourceId + actionType + yyyy-mm-dd` 같은 idempotency key를 만들고, DB에는 unique index를 둡니다. 코드에서만 중복 체크하면 동시 실행 때 뚫립니다.
AI가 만든 코드에서 의외로 자주 빠지는 건 타임존입니다. `new Date()`로 오늘 날짜를 잘라 쓰는 코드는 로컬에서는 멀쩡하지만, 서버가 UTC면 한국 시간 오전 9시 전까지 전날로 처리될 수 있습니다. 날짜 기준 배치라면 “사용자 타임존 기준인지, 서버 타임존 기준인지, 비즈니스 타임존 기준인지”를 주석이 아니라 설정값으로 분리해야 합니다.
제가 쓰는 최소 감사 순서는 이렇습니다. 먼저 환경 변수와 기본값, 그다음 외부 API 상태 코드와 재시도, 그다음 DB 마이그레이션과 파괴적 액션, 마지막으로 관측 로그와 중복 실행 방지입니다. 코드 예쁘게 정리하는 건 그 뒤입니다. 예쁜 코드가 운영 사고를 막아주지는 않지만, 잘못된 기본값 하나는 바로 사고로 이어집니다.
AI가 만든 코드를 배포할 때 “잘 돌아가냐”보다 “실패했을 때 안전하게 멈추냐”를 기준으로 보면 체크 포인트가 확 줄어듭니다. 제 기준에서는 dry-run 없는 삭제 작업, 202를 완료로 보는 API 처리, unique index 없는 중복 방지 로직은 배포 보류입니다. 이 세 가지는 리뷰에서 발견되면 기능이 거의 끝났어도 다시 잡고 갑니다.
댓글 0