Back to list

exit 0 으로 끝난 검토 실패 3건과 원인 진단

0 Views
개발 도구 신뢰성
#LLM 에이전트#Claude Code#코드리뷰#자동화#회고

포트폴리오 사이트의 저장 구조를 바꾸는 계획을 세웠다. 되돌리기 어려운 변경이라 착수 전에 검토를 받기로 했다. 저장소를 모르는 검토자에게 계획서만 통째로 넘겨서 계획이 그것만으로 서는지 보는 방식이다.

세 경로를 차례로 시도했다. 셋 다 결과 없이 끝났다. 셋 다 실패처럼 보이지 않았다.

첫 번째. 훅이 고른 스킬

프롬프트에 "ai slop" 이라고 썼더니 훅이 스킬 하나를 자동으로 걸었다.

[MAGIC KEYWORD: AI-SLOP-CLEANER]
Skill routing detected: ai-slop-cleaner
IMPORTANT: Start the ai-slop-cleaner workflow immediately.

스킬은 붙었다. 그런데 그 스킬 설명의 첫 줄은 이렇다.

Clean AI-generated code slop with a regression-safe, deletion-first workflow

코드를 정리하는 도구였다. 내가 다루던 것은 한국어 문장이었다. 키워드는 맞았고 대상이 틀렸다. 그대로 따랐다면 계획서를 읽는 대신 코드 정리가 돌았을 것이다.

두 번째. 본문 없이 돌아온 서브에이전트

다음은 서브에이전트에 맡겼다. 도구 사용을 금지하고 계획서 전문을 넣었다. 형식도 지정했다. 마지막 줄에 VERDICT: APPROVE 또는 VERDICT: ITERATE 를 쓰라고 했다.

돌아온 것은 이것뿐이었다.

{"type":"idle_notification","from":"plan-critic-r1","idleReason":"available"}

본문이 없다. available 이라는 단어에 걸렸다. 사람이 읽으면 "할 일을 마치고 대기 중" 으로 읽힌다. 실제로는 그냥 놀고 있다는 신호였고 검토 결과와는 상관이 없었다.

전문을 다시 요청했다. 같은 신호가 또 왔다. 에이전트 종류를 바꿔 한 번 더 돌렸다. 세 번째도 같았다. 세 번 다 오류로 끝나지 않았다.

세 번째. 프롬프트를 되돌려준 codex

가장 오래 속은 것이 이쪽이다.

첫 실행은 오류가 분명했다.

Not inside a trusted directory and --skip-git-repo-check was not specified.

플래그를 붙이고 다시 돌렸다. 이번에는 출력 파일이 15,135바이트로 커졌다. 뭔가 나온 것처럼 보였다. VERDICT 로 grep 했더니 52번째 줄에서 잡혔다.

그 52번째 줄은 내가 쓴 프롬프트 안의 형식 안내였다. codex 는 입력을 그대로 되돌려주고 끝냈다. 파일이 커진 이유는 프롬프트가 길었기 때문이다.

진짜 원인은 파일 맨 끝 두 줄에 있었다.

{"type":"error","status":400,"error":{"type":"invalid_request_error",
"message":"The 'gpt-5.6-sol' model is not supported when using Codex with a ChatGPT account."}}

설정 파일에 적힌 기본 모델을 계정이 지원하지 않았다. 모델 이름을 하나씩 넣어 봤다. gpt-5.1-codex, gpt-5.1, gpt-5, gpt-5-codex, o3, gpt-5.6, gpt-5.6-codex, gpt-5.4, gpt-5.3, gpt-5.2, gpt-5.1-codex-max, codex-1, o4-mini, gpt-4.1. 전부 같은 400 이었다.

16개를 넣어 보고 통과한 것은 gpt-5.5 하나였다.

지침에 이미 적혀 있던 함정 9개

여기서 불편한 사실이 하나 있다. 내 작업 지침 파일에는 codex 함정이 이미 9개 적혀 있었다.

stdin 을 기다려 멈춘다
Windows 샌드박스는 셸 spawn 에 실패한다
| tail 로 파이프하면 종료까지 버퍼링된다
--dangerously-bypass 는 auto-mode classifier 가 차단한다
백그라운드로 돌리면 프롬프트만 에코하고 응답을 생성하지 않는다
"$(cat ...)" 가 큰 프롬프트에서 Argument list too long 으로 실패한다
...

다섯 번째 줄이 이번 증상과 거의 같다. 프롬프트만 되돌려주고 응답이 없다는 것. 다만 그 줄은 원인을 백그라운드 실행이라고 적어 뒀다. 나는 앞에서 돌리고 있었으니 해당 없다고 넘겼다. 증상이 같아도 원인이 다를 수 있다는 데까지 생각이 가지 않았다.

같은 지침에는 대비책도 적혀 있었다. codex 가 안 되면 서브에이전트 검토로 대신하라는 것이다. 그 대비책이 두 번째 실패였다. 대비책이 원본과 같은 방식으로 망가져 있었다.

세 실패의 공통점

원인은 셋 다 달랐다. 대상이 틀린 스킬, 신호를 잘못 읽은 것, 모델 설정. 공통점은 원인이 아니라 겉모습에 있었다.

실제로 일어난 일화면에 보인 것
코드용 도구가 문장 작업에 붙음스킬 연결 성공
결과 없이 대기 신호만 돌아옴available
입력을 되돌려주고 끝냄파일 15KB, exit 0

셋 다 종료 코드나 상태 문자열만 보면 정상이다. 검토가 돌지 않았다는 사실을 검토 결과로는 알 수 없었다.

성공 판단 기준의 교체

모델을 바꾸고 세 라운드를 돌렸다. ITERATE, ITERATE, APPROVE.

이때부터 성공 여부를 과정이 아니라 결과 파일로 봤다. 마지막 줄에 VERDICT 가 없으면 실패로 친다. 파일 크기도, 종료 코드도, 상태 문자열도 보지 않는다.

grep -n "^VERDICT" critic-out.md | tail -1

이 한 줄이 안 나오면 검토는 없던 것이다.

검토가 잡은 설계 구멍

다시 돌렸다. 1라운드에서 계획대로 안 되는 자리가 나왔다.

translations.en = null 또는 "전부 공백"을 en 키 삭제로 정의했는데, 시드는 "키가 없을 때만 EN을 채움"이다. 즉 관리자가 번역을 회수해도 다음 배포 때 시드가 다시 EN을 넣는다.

내가 만든 설계는 삭제를 "키 없음" 으로 표현했다. 그런데 배포 때 도는 스크립트는 "키 없음" 을 "아직 안 채웠음" 으로 읽었다. 두 상태가 같은 모양이었다. 지운 번역이 다음 배포에 되살아난다.

못 본 채로 착수했다면 관리자가 번역을 지우고 배포한 뒤에야 알았을 것이다. 그때는 원인이 훨씬 멀어 보인다.

지운 표시를 따로 남기는 방법도 있었다. 그보다 되살아나는 경로를 아예 없애는 쪽이 나았다. 배포 과정에서 그 스크립트를 뺐다.

남은 것

검토 도구 안에 "결과가 비면 실패" 판단이 아직 없다. 지금은 내가 grep 으로 확인한다. 자동으로 만들려면 검토를 감싸는 쪽이 결과를 읽고 비었을 때 0이 아닌 코드로 끝나야 한다.

지침 파일에는 줄 하나를 더 적었다. 증상이 같으면 원인도 같다고 보지 않는다.


Comments

Write a Comment