쓸 만한 GitHub 프로젝트를 찾아 소개하는 일을 매번 직접 시작하지 않아도 되도록 에이전트에게 예약해 뒀어요. 그런데 2026년 10월 7일 오전 9시 탐구 실행 한 건이 2.2분 만에 계획 검증에서 실패했고, 자동 복구도 1회 시도 뒤 중단됐어요.

무슨 상황이었냐면요

이 블로그에서는 Codex가 자료를 찾아 글 계획을 세우고 원고를 쓰면, Claude가 최종 검수하고 실행기가 검사와 공개를 이어 맡아요. GitHub는 프로젝트의 코드를 보관하고 공유하는 서비스이고, 여기서 소개하려는 원본 저장소 주소를 계획에 적게 해 뒀어요. 글을 쓰기 전 검증기가 그 주소를 읽어 이미 소개한 프로젝트인지, 수정하려는 글과 같은 프로젝트인지 확인하는 구조예요. 이날은 주소가 적힌 계획을 검증기가 읽지 못해서, 원고를 쓰는 단계로 넘어가지 못했어요.

수정은 한 줄에서 시작했어요. 사람이 보기에는 같은 주소인데 프로그램은 콜론과 등호를 다르게 읽고 있었거든요. 주소를 빠뜨린 계획과 주소를 못 읽는 프로그램을 같은 오류로 표시해 둔 탓에, 처음 볼 곳도 달라졌거든요.

오류는 주소가 없다고 했어요

오전 9시 실행의 오류는 이렇게 남았어요.

GitHub 탐구 계획 why에 repository: 원본 저장소 URL이 필요합니다.

why는 왜 이 글을 쓰려는지 적는 계획의 설명란이에요. repository는 코드가 들어 있는 저장소를 뜻하고, URL은 그 저장소로 찾아갈 웹 주소예요. 오류 문장만 읽으면 설명란에 원본 주소가 없거나, 주소를 잘못 적었다고 받아들이기 쉬워요. 자동 복구가 중단됐다는 기록까지 함께 보였으니 계획 내용을 먼저 살펴볼 이유가 있었어요.

그런데 오전 9시 2분 운영 점검은 다른 단서를 확인했어요. Graphify를 소개하려던 계획의 설명란에 다음 표기가 있었어요.

repository=https://github.com/Graphify-Labs/graphify

출처 목록인 sources에도 저장소 주소가 있었어요. 출처 목록은 자료를 확인할 때 쓰는 주소 모음이에요. 적어도 이번 실패를 “저장소 주소를 빼먹었다”로 설명할 상황은 아니었어요. 설명란과 출처 목록을 함께 보니, 주소가 존재하는데도 검증기가 없다고 말하고 있었던 거예요.

같은 점검은 설명란에 함께 있던 format=…, category=… 같은 표기에 이끌려 저장소도 등호로 쓴 것으로 보인다고 적었어요. format은 글의 형식, category는 글의 분류예요. 이 둘의 영향은 로그를 바탕으로 한 추정이에요. 확실히 확인한 것은 모델이 repository=를 썼고 검증기는 repository:만 인식했다는 차이였어요. 모델이 왜 그 문장부호를 골랐는지까지 밝혀야 파서를 고칠 수 있는 문제는 아니었어요.

콜론 하나를 찾던 코드였어요

파서는 입력 문장에서 필요한 정보를 읽어 내는 프로그램이에요. 이번에는 계획 설명란에서 저장소 주소를 뽑는 정규식이 그 역할을 했어요. 정규식은 어떤 글자 모양을 찾을지 적어 둔 규칙이고요. 원래 규칙의 핵심은 이렇게 생겼어요.

/\brepository:\s*/

repository 다음에 콜론이 바로 있어야 맞는 입력으로 읽어요. \s*는 그 뒤에 공백이 있어도 된다는 뜻이에요. 사람은 등호 뒤에 붙은 주소도 저장소 주소라고 읽을 수 있지만, 이 규칙은 정해 둔 모양에 맞는 글자만 찾았어요. 그래서 주소 자체를 검토하기도 전에, 주소를 추출하는 단계에서 빈 결과가 나왔어요.

오전 11시 36분에는 Graphify 계획을 기존 실행기의 check-only 방식으로 수동 검증했어요. check-only는 계획 검사만 하고 글 작성이나 발행은 진행하지 않는 점검이에요. 아침에 거절됐던 repository= 문장이 그대로 들어간 계획이었는데, 이번에는 오류 없이 약 2초 만에 “점검만(계획 있음)”으로 끝났어요. 글 작성은 이후 전체 실행에서 확인할 일이었어요.

변경을 묶어 저장하는 Git 커밋에는 오전 11:37:27 KST라는 시각과 다음 메시지가 남았어요. KST는 한국 표준시예요.

4f1efccc784635d49f04a3890f7978eed94ed1b4
fix: GitHub 탐구 계획의 등호 저장소 표기를 허용

이 커밋은 저장소 표기에 등호도 허용하도록 고쳤다는 변경 기록이에요. github-guides.mjs의 주소 추출 규칙에서 바뀐 부분은 다음과 같았어요.

/\brepository\s*[:=]\s*/

[:=]는 콜론 또는 등호 중 하나를 받는다는 뜻이에요. 그 앞뒤의 \s* 덕분에 repository = 주소처럼 띄어 쓴 입력도 읽어요. 위 코드는 변경의 핵심 부분이고, 실제 전체 규칙에는 뒤이어 https://github.com/으로 시작하는 주소를 읽는 조건이 붙어 있어요. 아무 주소나 받아 주도록 검사를 없앤 수정은 아니었어요.

같은 커밋에는 등호를 쓴 계획과, 공백·백틱을 함께 쓴 계획을 받는 테스트가 추가됐어요. 백틱은 코드나 값을 감쌀 때 쓰는 작은 역따옴표예요. 반대로 GitHub가 아닌 example.com 주소를 등호 뒤에 넣었을 때는 거부하는 테스트도 들어갔어요. 입력 모양은 넓히면서 원본 저장소 조건이 남는지 같이 확인한 셈이에요.

한 줄을 고친 뒤에도 실패 표시는 남았어요

기록의 흐름을 모으면 09:00 실패 → 09:02 표기 차이 확인 → 11:36 계획만 점검 → 11:37:27 수정 커밋 → 20:30 실제 실행 확인 대기 → 23:14 탐구 발행 → 23:21 운영 정상으로 이어져요. 전부 2026년 10월 7일의 한국 시각이에요.

여기서 눈에 띄는 것은 오전에 코드를 고쳤는데도 운영 화면에는 실패 이력이 오래 남았다는 점이에요. 오전 9시 2분부터 오후 8시 30분까지 점검 기록은 GitHub 탐구의 실패와 자동 복구 상태를 계속 표시했어요. 새 실패가 생긴 게 아니라 마지막 실패 기록이 화면에 남아 있었어요.

오후 8시 30분 기록에는 이렇게 적혔어요.

두 실패 모두 원인 수정이 이미 반영됐고, 실제 정상 실행 확인만 남았습니다. 다음 예약 실행을 기다립니다.

“두 실패”는 당시 함께 점검하던 GitHub 탐구와 AI 사용 통계 작업을 가리켜요. 이 문장이 말하는 단계는 수정 완료와 실제 업무 회복 사이예요. 소스 코드에 새 규칙이 들어간 것과, 실행기가 그 규칙으로 계획을 읽고 글을 끝내는 것은 별도로 확인할 일이었어요.

숫자도 그 단계와 함께 읽어야 했어요. 아침 실행의 2.2분은 실패한 작업이 걸린 시간이고, 처음 기록된 1회는 자동 복구의 시도 횟수예요. 원고 한 편을 완성한 시간이나 같은 오류가 한 번 더 재현됐다는 횟수로 바꿔 읽을 수는 없어요. 저녁에는 복구 상태가 정상 실행 확인 대기로 바뀌고 시도 수가 2회로 표시됐어요. 이것 역시 복구 상태 기록의 숫자예요. 재시도마다 무엇을 했는지 숫자만으로 덧붙이지 않았어요.

오후 11시 14분 GitHub 탐구는 발행, 소요 시간 14.7분으로 남았어요. 이 실행은 예약 시각을 기다린 것이 아니라, 대기 중인 작업을 실제로 돌려 보자는 사용자 요청으로 시작한 수동 실행이었어요. 이때 이어 간 글은 아침의 Graphify가 아니라 OpenHands Agent Canvas 탐구 글이었어요. 아침 계획을 그대로 공개한 결과와 혼동하면 안 되겠더라고요. 다만 이 계획에도 repository=https://github.com/OpenHands/OpenHands처럼 등호 표기가 들어 있었어요. 고친 검증기가 등호 표기를 읽고 작성·검수·발행까지 이어 간 실제 사례가 된 셈이에요. Graphify 계획 자체는 수동 계획 점검과 테스트로 확인했고요.

오후 11시 21분 운영 점검에는 35개 항목 모두 정상이라고 남았어요. 운영 점검이 살펴본 35개 항목이 모두 통과한 거예요. 이 점검에도 자동 복구 목록은 정상 실행 확인 대기라는 흔적이 함께 남아 있었어요. 현재 점검의 정상 여부와 복구 목록 정리 상태도 서로 다른 기록으로 읽었어요.

의미 검사 앞에는 모양을 읽는 단계가 있어요

이번 일을 다른 자동화에 가져갈 때 도움이 되는 구분은 표기를 읽는 단계와 읽어 낸 값이 맞는지 판단하는 단계예요. repository=주소와 repository: 주소에서 같은 주소를 얻는 것은 첫 단계예요. 그 주소가 GitHub 원본 저장소인지, 이미 소개한 대상인지, 수정할 글의 저장소와 맞는지 보는 것은 다음 단계고요.

첫 단계가 실패하면 두 번째 단계에 도착하지 못해요. 그런데 둘 다 “원본 저장소 URL이 필요합니다”라고 끝내면, 사용자는 자료가 부족해서 막혔는지 입력 모양 때문에 막혔는지 구분하기 어려워져요. 에이전트에게 주소를 다시 찾게 시켜도 이미 있는 주소를 다른 모양으로 돌려받는 데 그칠 수 있어요.

자연어 계획에는 설명과 실행에 필요한 값이 한 문장에 섞여요. 사람이 설명을 자유롭게 쓰도록 해 두고 프로그램은 문장부호 하나만 받으면, 경계에서 이런 일이 생겨요. 실제 계획에서 나온 변형을 받아 주되, 읽어 낸 값에 대한 검사는 그대로 두는 방식이 이번 사건에 맞았어요. 콜론만 허용하던 입력 규칙을 풀었다고 해서 원본 URL·중복·승인 계획 일치 검사를 함께 풀 이유는 없었어요.

그래서 주소를 다시 쓰게 하기 전에 확인해요

첫째, 누락 오류가 나면 계획 본문과 출처 목록을 함께 봐요. 같은 주소가 한쪽에만 있는지, 양쪽에 있는데도 추출이 실패했는지 확인하면 다음에 고칠 곳을 좁힐 수 있어요. 이번에는 두 곳에 주소가 있었기 때문에 검색 자료를 더 늘리는 일이 해결책이 되지 않았어요.

둘째, 실제 실패 입력을 테스트로 남겨요. 콜론과 등호, 공백과 백틱을 받는 사례에 더해 잘못된 도메인을 거부하는 사례를 같이 두면, 허용 범위를 넓힌 뒤에도 남겨야 할 경계가 보이거든요. 자기 프로그램의 테스트에서는 주소 추출과 중복·수정 대상 일치 검사를 각각 확인해요.

셋째, 점검만 하는 경로와 전체 업무 경로를 따로 확인해요. 계획 검사가 통과했다고 바로 발행을 반복하기보다, 입력 검사만 실행하는 방식으로 먼저 좁혀 볼 수 있어요. 그다음 실제 업무가 작성과 검수를 끝내는지는 다른 증거로 남겨요. 공개 작업의 재실행에는 중복 게시 여부도 함께 따라오니까요.

자기 환경에서 Node.js를 쓰고 있다면 아래 명령으로 이번 표기 차이의 핵심을 직접 비교할 수 있어요. 외부 접속이나 파일 변경 없이 가상의 주소를 읽는 짧은 점검이에요.

node -e 'const s="repository=https://github.com/example/demo"; console.log({colonOnly:/\brepository:\s*/.test(s), colonOrEquals:/\brepository\s*[:=]\s*/.test(s)})'

colonOnly: false는 콜론 전용 규칙이 입력을 놓쳤다는 뜻이고, colonOrEquals: true는 등호도 받는 규칙이 입력을 읽었다는 뜻이에요. 이 짧은 명령은 문장부호 차이만 비교해요. 자기 실행기에 적용할 때는 추출한 URL을 검증하는 테스트와 실제 작업 확인이 뒤따라요.

넷째, 과거 실패를 성공으로 덮어쓰지 않아요. 오전의 실패, 검사만 한 실행, 밤의 발행을 각각 남겨 두면 어느 단계가 회복됐는지 설명할 수 있어요. 같은 실패 이력을 계속 표시한 점검 횟수를 새로운 사고 횟수로 세지도 않고요.

마치며

이번에는 주소를 다시 찾는 대신, 이미 적힌 주소를 프로그램이 어떻게 읽는지 확인하고 허용 표기와 테스트를 함께 고쳤어요. 여러분의 에이전트가 “필수 값이 없다”고 멈췄을 때, 그 값이 정말 없는지와 다음 프로그램이 그 표기를 읽을 수 있는지를 나눠 확인해 보셨나요?