
'saved'라고 찍고 일주일 전 이미지를 내밀었다 — 조용히 죽은 AI 파이프라인 추적기
지난주까지 멀쩡히 돌던 커버 자동생성이, 어느 날 갑자기 일주일 전에 만든 엉뚱한 이미지를 내밀었습니다. 그런데 스크립트는 태연했습니다 — 로그엔 saved, 종료 코드는 0. 시스템은 "다 됐다"고 말하고 있었습니다.
가장 무서운 실패는 요란하게 터지는 실패가 아닙니다. 성공한 척하는 실패입니다. 에러도 경고도 없이, 결과만 조용히 틀려 있습니다.
이 글은 그 조용한 거짓말을 쫓아 결국 오픈소스 CLI의 소스코드 한 줄까지 내려갔다가, 명령 한 줄로 되살린 기록입니다. 요점은 셋입니다. ① exit 0은 성공의 증거가 아니다, ② 범인은 대개 내가 건드린 곳이 아니라 내가 안 건드린 곳에 있다, ③ 추측하지 말고 증거로 용의자를 지워라.
왜 '성공'이 가장 위험한 실패인가
파이프라인은 단순했습니다. AI CLI에게 이미지를 생성시키면, 그 도구가 결과를 특정 폴더에 저장합니다. 그러면 제 스크립트가 그 폴더에서 가장 최근 파일을 집어 옵니다.
허점은 여기 있었습니다. 생성이 조용히 실패해서 새 파일이 안 생기면, "가장 최근 파일"은 지난주에 만든 옛 이미지가 됩니다. 스크립트는 그걸 집어 들고 saved를 찍습니다. 아무것도 안 만들었는데 성공했다고 보고하는 거죠.
핵심 — 부작용(폴더의 최신 파일)으로 성공을 판정하면, 그 부작용이 안 일어난 순간 신호가 거짓말을 한다.
검증 없는 파이프라인은 실패를 삼키는 게 아니라 거짓말을 전파합니다. 이게 첫 번째 교훈이었습니다.
용의자를 하나씩 지운다
당연히 처음엔 제 잘못을 의심했습니다. 프롬프트가 이상한가, 경로가 틀렸나, 권한이 없나. 하지만 추측으로 고치기 시작하면 끝이 없습니다. 그래서 증거로 하나씩 지워 나갔습니다.
- 인증? — 로그인 상태 멀쩡("Logged in"). 지움.
- 쿼터? — 유료 계정에 사용량도 10% 남아 있었습니다. 지움.
- 작업 디렉터리? — 알고 보니 도구가 제 개인 지식 볼트의 설정 파일을 읽고 엉뚱한 경로로 스스로를 다시 호출하는 재귀 루프에 빠지고 있었습니다. 깨끗한 빈 폴더에서 돌리니 그 증상은 사라졌지만 — 이미지는 여전히 안 나왔습니다. 부분 원인이지 본범은 아니었습니다.
- 샌드박스? — 윈도우 샌드박스가 프로세스를 못 띄우고 "액세스 거부"로 죽었습니다. 우회하니 이번엔 도구가 대놓고 말했습니다. "이 세션엔 이미지 생성 도구가 없습니다."
이 한 문장에서 방향이 확정됐습니다. 문제는 제 설정이 아니라 도구가 이미지 생성 능력 자체를 잃은 것이었습니다. 그것도 헤드리스로 부를 때만.
진짜 범인 — 소스코드의 게이트 한 줄
도구를 최신 버전으로 올린 게 화근이었습니다. 그 버전에 **"무료 플랜 계정은 이미지 생성을 막는다"**는 변경이 들어가 있었습니다(공개된 PR입니다). 코드로 옮기면 딱 한 줄입니다 — 계정 플랜 == 무료 → 이미지 도구 제외. 대화형 앱이든 헤드리스든 같은 경로를 탑니다.
그런데 저는 유료입니다. 왜 걸렸을까. 원인은 어이없을 만큼 사소했습니다. 캐시된 로그인 토큰이 만료돼, 그 안에 박힌 오래된 '무료' 값이 읽힌 것이었습니다. 실제 결제 상태와 무관하게, 상한 토큰 하나가 저를 무료 사용자로 둔갑시켰습니다. 같은 증상을 겪은 유료 사용자들의 공개 이슈가 이미 여럿 올라와 있었습니다.
정리하면, 증상은 "도구가 사라짐"이었지만 진짜 원인은 정책 게이트 + 상한 토큰의 합작이었습니다. 겉과 속이 이렇게 먼 버그는 소스를 안 읽으면 며칠도 헤맵니다.
되살리는 길은 둘이었습니다.
- 재로그인해서 토큰을 새로 받는다 — 지금 유료면 통합니다. 하지만 다음 달 무료로 내려가면 또 막힙니다.
- 게이트가 생기기 직전 버전으로 핀한다 — 플랜과 무관하게 동작합니다.
저는 플랜에 안 휘둘리는 두 번째를 택했습니다.
# 게이트가 들어오기 전 마지막 버전으로 고정
npm i -g @openai/codex@0.145.0
# 그리고 설정 파일(~/.codex/config.toml)에 한 줄 —
# 조용히 최신으로 다시 올라가지 못하게 핀
check_for_update_on_startup = false
정직하게 짚자면, 이건 무료 플랜에서 '공짜 이미지 생성'이 되는 이야기가 아닙니다. 클라이언트 게이트를 지워도 서버가 막을 수 있으니까요. 유료인데 상한 토큰에 잘못 걸린 저를 되살린 것뿐입니다.
반드시 챙길 함정
- exit 0을 성공으로 믿지 마라. 부작용으로 성공을 판정한다면, 산출물의 신선도(방금 만들어졌나)를 검증에 넣으세요. "파일이 있다"가 아니라 "이번에 생겼다"를 물어야 합니다.
- 재귀 라우팅. 도구가 작업 폴더의 설정을 읽고 스스로를 다시 호출하는 루프에 빠질 수 있습니다. 민감한 자동화는 깨끗한 빈 디렉터리에서 돌리세요.
- stdin 무한 대기. 헤드리스로 CLI를 부를 때 표준입력을 안 닫으면 "입력 대기"로 영원히 멈춥니다. 빈 입력을 파이프로 물려 닫아주세요.
- 자동 업데이트는 편의이자 지뢰. 잘 돌던 도구가 소리 없이 업데이트되며 동작을 바꿉니다. 파이프라인이 의존하는 도구는 버전을 고정하고, 올리는 건 의도적으로만 하세요.
정리
- 성공 신호를 의심하라 —
exit 0도saved도 증거가 아니다. 결과의 신선도까지 봐야 진짜 검증이다. - 범인은 내가 안 건드린 곳 — 인증·쿼터·경로·샌드박스를 증거로 지워 나가면, 남는 게 진실이다.
- 소스를 읽어라 — 오픈소스라면 게이트 함수 한 줄이 몇 시간의 추측을 끝낸다.
- 의존 도구는 버전을 핀하라 — 그리고 업데이트는 사고가 아니라 결정으로.
이 '조용한 실패'는 제가 전에 쓴 스킬 편에서 다룬 "에러 없이 삼켜지는 실패"와 정확히 같은 계열입니다. 가장 위험한 버그는 소리를 지르지 않습니다.
오후 하나를 통째로 태운 범인이 내 코드가 아니라 남의 업데이트 한 줄이었다는 걸 알았을 때는 허탈함과 안도가 반반이었습니다 — 그 뒤로 저는 무엇보다 '성공'이라는 말부터 의심합니다.