글

라벨이 테스트인 게시물 표시

텔레그램 한 줄로 블로그 작업을 넘길 때 중복 실행을 막는 구조

이미지
답부터 말하면 텔레그램 메시지 ID를 요청 파일 이름과 처리 영수증의 공통 키로 써야 한다. 같은 메시지가 다시 들어와도 새 파일로 덮어쓰지 않고, 상태 파일이 있으면 에이전트에 다시 넣지 않는 구조다. 이 결론은 2026년 9월 28일 로컬 테스트 기준이다. 테스트 41개가 통과했고 실패는 0개였다. 다만 실제 텔레그램 사진 왕복과 장애 복구 시간은 아직 측정하지 않았다. 왜 명령 한 줄이 두 번 실행될 수 있나? 텔레그램에서 /blog 명령을 한 번 보냈다고 실행도 한 번이라고 볼 수 있을까. 수신 플러그인, 파일 감시, 에이전트 세션, 답장 전송 사이에는 각각 재시작과 재호출이 들어올 수 있다. 문제는 요청 내용이 아니라 경계다. 같은 업데이트를 플러그인이 다시 받거나, 파일 감시 이벤트가 반복되거나, 답장 전송 중 프로세스가 멈출 수 있다. 그래서 “이미 본 요청인가”를 각 단계가 같은 기준으로 판단해야 한다. 이 브리지는 메시지 본문 대신 텔레그램 메시지 ID를 기준으로 삼는다. 내용이 같은 두 요청은 서로 다른 작업일 수 있지만, ID가 같은 요청은 같은 전달 건이기 때문이다. 요청 파일은 어떻게 한 번만 만들어지나? 수신 단계는 먼저 임시 파일에 요청 전체를 쓴다. 파일을 동기화하고 닫은 뒤, requests/<메시지ID>.json 이라는 최종 이름에 배타적으로 연결한다. 최종 파일이 이미 있으면 덮어쓰지 않는다. 기존 파일 자체가 중복 수신 영수증이 된다. 소비자는 완성된 이름만 보므로 절반만 써진 JSON을 집어 들지 않는다. 임시 파일에 요청 전체를 기록한다. 파일 동기화와 닫기를 끝낸다. 메시지 ID 이름으로 배타적 링크를 만든다. 같은 이름이 있으면 기존 요청을 유지한다. 요청 파일은 처리 뒤에도 삭제하지 않는다. 2026년 9월 28일 테스트에는 같은 명령을 8개 동시에 넣어도 최종 요청 파일이 하나만 남는 검사가 있다. 파일 감시자가 처음 본 시점에 JSON 전체를 읽을 수 있는지도 별도로 확인한다...

테스트 113개 다 통과했는데 실제론 0개를 긁었다 — AI가 짠 수집기의 함정

이미지
AI에게 수집기(웹에서 데이터를 긁어오는 코드) 두 개를 맡겼더니, 테스트 113개를 전부 통과시켰다. 초록불. 그런데 실제로 돌려보니 한쪽은 0개를 긁었다. 전에 이 블로그에서 이 트렌드 수집 도구의 데이터 소스가 어떻게 죽는지 썼다. 이번엔 거기에 기능을 붙이다가 더 불편한 걸 배웠다. 테스트는 통과했는데 코드는 작동하지 않았다 — 이게 어떻게 동시에 참일 수 있나. 한 줄이면 이렇다. 추정한 입력에 맞춰 짠 테스트는 그 추정이 맞다는 것만 증명한다. 실제 세계가 추정과 같은지는 아무도 검증하지 않았다. 누가 읽으면 좋은가 AI(Claude·Cursor 등)에게 코드를 맡기고 테스트 초록불을 받아본 사람 특히 외부 API나 웹페이지를 긁는 코드를 AI에 시켜본 사람 “테스트 다 통과했으니 됐겠지”로 넘어간 적 있는 사람 113개 초록불, 라이브에선 0개 무슨 일이었나부터. 트렌드 도구에 “해외에서 잘나가는 신제품”을 자동으로 잡아낼 수집기 두 개를 붙이려 했다. 하나는 Show HN(해커뉴스 신제품 게시판), 하나는 Product Hunt(신제품 등록 사이트). 작업은 서브에이전트에 맡겼다. 테스트를 먼저 쓰고 구현하는 방식으로, 규칙도 빡빡하게 줬다. 에이전트는 코드와 테스트를 짜서 “113개 통과”를 보고했다. 여기서 멈췄으면 끝이었다. 초록불이니까. 그런데 안 믿고 실제로 돌렸다. Show HN은 실제 런칭 5건을 정확히 긁어왔다. Product Hunt는 0개였다. 대신 에러 하나 — “제품을 하나도 파싱하지 못함”. 테스트는 왜 통과했나 이게 핵심이다. 0개를 긁는 코드의 테스트가 어떻게 113개나 통과했나. 답은 단순했다. 에이전트는 Product Hunt의 실제 응답을 한 번도 본 적이 없다. 줄 수도 없었다. 그 응답은 실제로 한 번 긁어봐야 나온다. 그래서 “이렇게 생겼겠지” 하고 가짜 샘플을 만들었고, 그 샘플에 맞춰 파싱 코드를 짰고, 다시 그 샘플로 테스트를 돌렸다. 테스트는 “추정이 추정과 일치...