자동화를 만들어 두고 가장 무서운 순간은 언제일까요? 알람이 울리는 순간이 아닙니다. 아무 일도 없는 것처럼 조용한 순간입니다.
2026년 8월 8일 아침, 블로그에 새 글이 하나 올라와 있었습니다. 글은 멀쩡했고 본문 이미지만 없었습니다. 전날 만든 이미지 자동 생성 장치의 첫 무인 실행이 실패한 것이었습니다.
알림 메일도 있었고 실패 알람도 있었습니다. 그런데 제가 그걸 열어 보기 전에 사람 눈이 먼저 찾아냈습니다.
더 곤란한 사실은 그다음이었습니다. 이 파이프라인에는 하루 전에 붙여 둔 회귀 검사(regression test), 즉 고친 것이 다시 깨졌는지 자동으로 확인하는 장치가 두 겹 있었습니다. 표본 14종을 렌더해 규격을 재는 검사였고, 그 시점에 전부 초록불이었습니다.
이 글에서는 초록불 두 개를 통과한 결함이 어떻게 무인 실행을 세웠는지 실제 로그와 수치로 정리했습니다. 결론부터 말하면 문제는 검사의 정확도가 아니라 검사의 사정거리였습니다.
1. 32초 만에 끝난 첫 무인 실행
실패는 이미지를 만들다가 난 것이 아니라, 만들기 전 검증에서 났습니다. 그래서 32초로 끝났습니다.
이 파이프라인은 두 조각으로 나뉘어 있습니다. 클라우드 루틴이 이미지 설계도(스펙)만 JSON으로 적어 보내고, 깃허브 액션이 저장소를 내려받아 실제 그림을 렌더합니다. 루틴은 저장소에 접근할 수 없어 그림을 직접 그리게 하면 좌표가 매번 어긋나기 때문입니다.
실행 기록은 이렇습니다.
| 항목 | 값 |
|---|---|
| 실행 시각 | 2026-08-08 02:28 UTC |
| 소요 | 32초 |
| 결과 | 실패 (스펙 검증 단계) |
| 직전 실행 | 8월 7일 23:01 UTC — 성공 |
러너가 남긴 실패 로그는 두 줄이었습니다.
images[3].icon: 태그 밖 내용은 넣을 수 없다
images[7].icon: 태그 밖 내용은 넣을 수 없다코드는 직전 성공 때와 한 글자도 다르지 않았습니다. 달라진 것은 입력, 즉 그날 루틴이 그려 보낸 아이콘이었습니다.
한 줄 정리: 코드가 그대로인데 결과가 바뀌면, 범인은 코드가 아니라 그날의 입력입니다.
2. 원인 — 허용 목록과 파서가 어긋나 있었다
허용 목록에는 있는 속성이었는데, 그 목록을 읽는 쪽이 그 속성을 못 읽었습니다.
루틴이 그려 보낸 아이콘은 통째로 믿지 않고 검사기를 한 번 거칩니다. 쓸 수 있는 태그와 속성만 통과시키는 허용 목록(whitelist) 방식입니다. 그 목록에는 선을 그을 때 쓰는 좌표 속성 x1 y1 x2 y2가 분명히 들어 있었습니다.
문제는 태그를 잘라내는 정규식이었습니다. 속성 이름을 알파벳만으로 받고 있었습니다.
// 속성명 자리에 0-9가 없다
/<\s*(\/?)([a-zA-Z]+)((?:\s+[a-zA-Z-]+\s*=\s*"[^"]*")*)\s*(\/?)>/g그래서 line 태그는 태그로 인식되지 않고 평범한 텍스트로 남았습니다. 텍스트가 태그 밖에 떠 있으니 검사기는 "태그 밖 내용"이라며 거부했습니다.
아이콘 8개 중 2개만 걸린 이유도 여기 있습니다. line은 그날 쓰인 태그 중 속성 이름에 숫자가 들어가는 유일한 태그였습니다. 나머지는 cx, r, d, points처럼 전부 글자뿐이었습니다.
| 아이콘 | 쓰인 태그 | 결과 |
|---|---|---|
| SEO 막대 | rect · polyline | 통과 |
| SEM 돋보기 | circle · line | 거부 |
| SERP 브라우저 창 | rect · circle | 통과 |
| 메타태그 꼬리표 | path · circle | 통과 |
| 백링크 사슬 | rect 2개 | 통과 |
| 오가닉 잎사귀 | path · line | 거부 |
여기서 하나 배웠습니다. 에러 메시지가 원인을 가리키지 않았습니다. "태그 밖 내용"은 증상이고, 진짜 원인은 "속성 이름을 못 읽었다"입니다.
검사기가 무언가를 거부할 때 거부 사유는 이렇게 한 겹 밀려서 나오기 쉽습니다. 의심스러우면 막는 쪽으로 만들어 뒀기 때문입니다. 안전한 설계의 대가죠.
한 줄 정리: 허용 목록과 그것을 읽는 파서를 따로 두면 둘은 언젠가 어긋나고, 그 어긋남은 "거부"라는 안전한 얼굴로 나타납니다.
3. 검사 두 겹이 왜 통과시켰나
둘 다 이 결함을 "봤지만 놓친" 것이 아닙니다. 애초에 보지 않았습니다.
전날 붙여 둔 장치는 두 개였습니다.
- 표본 회귀 검사 — 템플릿 14종을 실제로 렌더해 규격을 잰다. 그런데 이 검사는 이미지 킷을 직접 부릅니다. 루틴이 보낸 설계도를 검사기에 통과시키는 경로는 지나지 않았고, 결함이 있는 함수는 호출조차 되지 않았습니다.
- 깃허브 액션 자동 검사 — 관련 파일이 바뀌면 위 검사를 자동으로 돌린다. 그런데 대상 파일 목록(경로 필터)에 검사기 파일이 빠져 있었습니다. 그 파일을 고쳐도 액션은 아예 시작되지 않았습니다.
화면에는 초록 체크가 떠 있었습니다. 그 체크의 뜻은 "이 파일에 문제가 없다"가 아니라 "이 파일에 대해 아무 말도 하지 않았다"였습니다. 그런데 두 상태는 화면에서 똑같이 생겼습니다.
주차 단속에 비유하면 이렇습니다. 순찰차가 지나간 골목에 위반 차량이 없었던 것과, 순찰차가 그 골목에 들어가지 않은 것은 전혀 다른 일입니다. 그런데 보고서에는 둘 다 "위반 0건"으로 적힙니다.
한 줄 정리: 경로 필터와 표본 목록에 없는 것은 검사를 통과한 것이 아니라 검사를 받지 않은 것입니다.
4. 복구하다 알게 된 것 — "완료 표시"가 원본을 지우고 있었다
재시도는 설계해 뒀는데, 재작업은 설계하지 않았습니다.
실패한 실행 자체는 문제가 없었습니다. 설계도가 그대로 남아 다음 실행에서 다시 시도되도록 만들어 뒀으니까요. 문제는 성공한 뒤였습니다.
로컬에서 직접 렌더해 이미지 8장을 본문에 붙였습니다. 그 직후 검수에서 3장이 반려됐습니다. 다시 만들려고 설계도를 찾았는데, 없었습니다.
성공 시 동작: 본문 삽입 → 설계도를 비운다 (= 처리 완료 표시)완료 표시를 "설계도를 지우는 것"으로 구현해 뒀던 겁니다. 처리 여부를 따로 저장하지 않고, 남아 있으면 미처리로 본 구조였습니다. 간결하죠.
대신 한 번 성공하면 원본이 사라집니다. 살린 경로는 매일 새벽에 도는 자동 백업이었습니다.
그날 백업은 03:23이었고 이미지 삽입은 그 뒤였습니다. 한 시간만 어긋났으면 설계도는 사라졌습니다. 백업 주기가 하루라 운이 좋았을 뿐입니다.
한 줄 정리: "완료 표시"를 원본 삭제로 구현하면, 성공 직후에 발견되는 문제를 고칠 방법이 사라집니다.
5. 고친 방법 — 그리고 두 번 빗나간 계산
정규식을 한 줄 고치는 데는 5분이 걸렸고, 다시 안 나게 만드는 데 그보다 오래 걸렸습니다.
속성 이름 자리에 숫자를 허용하도록 정규식을 고쳤습니다. 허용 범위가 넓어진 것은 아닙니다. 무엇을 통과시킬지는 여전히 허용 목록이 정하고, 정규식은 그 목록을 제대로 읽는 역할만 합니다.
그리고 검사를 하나 새로 만들었습니다. 이번 검사는 양쪽을 다 봅니다.
- 허용 방향 — 통과시켜야 할 문법 15건이 실제로 통과하는가
- 거부 방향 — 막아야 할 문법 16건이 실제로 막히는가
이번 결함은 허용 방향이었습니다. 거부 케이스만 담은 검사였다면 영원히 잡히지 않았을 겁니다. 검사기를 만들 때 손이 먼저 가는 쪽은 "이게 막히나?"인데, 정작 무너진 쪽은 "이게 통과되나?"였습니다.
새 검사가 진짜로 작동하는지도 확인했습니다. 고친 코드는 그대로 두고, 결함을 되살린 사본을 따로 만들어 검사를 돌렸습니다.
현재 코드 : 통과
결함 되살림 : 거부 — "icon: 태그 밖 내용은 넣을 수 없다"빨간불을 먼저 본 뒤에 초록불을 믿었습니다. 통과만 확인하고 끝내면, 잘 만든 검사와 아무것도 검사하지 않는 검사를 구별할 수 없습니다. 이번 사고가 바로 후자였으니까요.
틀린 계산 — 회전한 도형의 높이를 두 번 빗나갔다
반려된 아이콘 하나를 다시 그리는 데 두 번 실패했습니다. 세트 안 다른 아이콘들은 60~71픽셀 높이였는데 이 아이콘만 31픽셀로 작아 보인다는 지적이었습니다.
| 시도 | 계산 근거 | 예상 | 실측 |
|---|---|---|---|
| 1차 | 사각형 외곽을 45도 회전 | 66px | 54px |
| 2차 | 둥근 끝을 반영한 식 | 64.8px | 65px |
끝이 완전히 둥근 막대는 외곽이 사각형이 아닙니다. 그래서 사각형을 회전시킨 값으로 계산하면 12픽셀을 헛짚습니다. 눈대중으로 맞추려다 두 번 다시 그린 뒤에야 식을 세웠습니다.
여담으로 이날 파일 하나를 통째로 깨뜨리기도 했습니다. 윈도우 파워셸로 한글 주석이 든 소스를 치환했다가 글자가 전부 깨지고 줄바꿈이 사라졌습니다. 백업에서 되살렸습니다. (한글이 든 소스는 편집기로 고치는 편이 안전합니다.)
한 줄 정리: 새로 만든 검사는 결함을 되살려 실패시켜 본 뒤에 신뢰합니다.
자주 묻는 질문
검사를 더 많이 붙이면 이런 일이 줄어드나요?
개수보다 사정거리가 먼저입니다. 이번에는 검사가 두 겹이나 있었는데도 결함이 있는 코드가 한 번도 실행되지 않았습니다.
새 검사를 붙이기 전에 "이 검사가 어느 코드를 실제로 통과시키는가"를 적어 보면 비어 있는 칸이 눈에 보입니다. 그 칸이 사고가 나는 자리입니다.
초록 체크를 어떻게 의심해야 할까요?
고친 파일 이름이 검사 대상 목록에 들어 있는지 직접 확인하는 것이 가장 빠릅니다. 자동 검사가 아예 실행되지 않았을 때도 화면은 초록이거나 조용합니다.
"검사가 통과했다"와 "검사가 돌지 않았다"를 구별해 주는 화면은 대체로 없습니다.
자동화가 실패했을 때 내 문제인지 아닌지 어떻게 아나요?
단계별 실패 로그가 있는지 보면 대체로 갈립니다. 특정 단계가 빨간불이면 내 코드 문제일 가능성이 높고, 단계 로그 없이 전체가 취소됐다면 실행 환경 쪽일 수 있습니다.
실제로 같은 주에 실행 환경이 흔들려 실패했다가 그냥 다시 돌리니 성공한 일도 있었습니다. 코드는 처음부터 옳았습니다.
마치며
이번 일에서 남은 문장은 하나입니다. "검사가 통과했다"와 "검사가 봤다"는 다른 말입니다. 그리고 화면에서는 둘이 똑같이 생겼습니다.
검사를 늘리는 일은 즐겁습니다. 초록불이 늘어나니까요. 하지만 늘어난 초록불이 실제로 무엇을 봤는지는 따로 확인해야 알 수 있습니다.
혼자 만들고 혼자 운영하는 자동화라면, 오늘 붙여 둔 검사 중 하나만 골라 일부러 깨뜨려 보는 것은 어떨까요? 빨간불이 안 켜지면, 그건 지켜 주고 있던 게 아닙니다.
이 글의 정보는 2026년 8월 12일 기준입니다.
같이 읽어 볼 글
- 자동 발행 루틴을 만들다 깨진 것 4가지 — 조용히 실패하는 자동화의 공통점
- AI 자동화 툴 3가지 — 재피어·메이크·n8n 초보자 가이드
- 클로드 코드 설치·사용법 — 윈도우에서 명령어 한 줄로 끝내기
