블로그 목록

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

제로컨텍스트 검토를 세 경로로 시도했고 셋 다 결과 없이 끝났다. 훅은 코드용 도구를 걸었고, 서브에이전트는 대기 신호만 보냈고, codex 는 프롬프트를 되돌려주며 exit 0 으로 끝났다. 셋 다 실패처럼 보이지 않았다.

공개 조회 0

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

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

첫 번째. 훅이 고른 스킬

프롬프트에 "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이 아닌 코드로 끝나야 한다.

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

다음부터

검토를 다른 도구나 에이전트에 맡길 때는 결과 마지막에 정해진 형식의 줄을 쓰게 하고, 종료 코드나 파일 크기나 상태 문자열이 아니라 그 줄이 있는지로 검토가 실제로 돌았는지 가른다. 대비 경로를 둘 때도 같은 확인을 대비 경로에 걸고, 알려진 증상과 겉모습이 같아도 원인까지 같다고 보지 않는다.

댓글

아직 보이는 댓글이 없습니다.

댓글 쓰기

댓글은 관리자가 승인한 뒤에 보입니다. 이메일은 공개하지 않습니다.

개인정보 수집·이용 동의 (필수)
  • 수집 항목: 이름, 이메일, 댓글 내용
  • 목적: 댓글 게시와 관리, 댓글에 관한 연락
  • 보유 기간: 이름과 내용은 댓글을 게시하는 동안, 이메일은 1년. 게시하지 않은 댓글은 90일 뒤에 지웁니다.
  • 스팸을 막으려고 접속 IP 주소를 90일 동안 저장합니다. 이 저장은 동의가 아니라 운영자의 정당한 이익에 근거합니다.
  • 동의를 거부할 권리가 있습니다. 거부하면 댓글을 쓸 수 없습니다. 자세한 내용은 개인정보처리방침에 있습니다.