수렴의 충격

유튜브 알림 봇, 부동산 수집기, 티켓 랭킹, 청첩장, 주간 가든봇, 그리고 이 사이트. 도메인도 언어도 다릅니다. Python과 TypeScript가 섞여 있고, 하는 일도 모니터링·수집·알림·콘텐츠로 제각각입니다. 공유하는 코드도 없고, 같은 사람이 만들었다는 사실 말고는 공통점도 없습니다.

그런데 지난 2주간 이들을 하나씩 손보며 고친 버그를 한자리에 늘어놓자, 소수의 결함 클래스로 수렴했습니다. 한 건의 실패가 전체를 끌어내리는 구조. 조용히 빠진 채 성공으로 집계되는 결손. 그리고 미리보기여야 할 경로가 운영 상태를 건드리는 부작용.

흩어진 작업을 교차로 훑어보면 이게 우연이 아닙니다. 같은 결함 클래스가 헬스체크를 6개 봇에 돌린 2주와 한 건의 실패가 전체를 무너뜨리지 않게 한 작업 양쪽에서 독립적으로 튀어나왔습니다. 같은 처방을 서로 베낀 게 아니라, 각자의 자리에서 같은 모양으로 무너진 뒤 같은 결론에 도달한 것입니다.

시간축 재발이 아니라 공간축 재발

같은 함정에 두 번 빠지면 체크리스트가 된다에서 다룬 건 "같은 곳에서 같은 실수 2회 = rule of two"였습니다. 한 레포 안에서 같은 함정이 시간 간격을 두고 재발하면, 그 한 곳에 자동 검사를 박거나 구조를 재설계합니다. 재발의 축이 시간입니다.

이번에 본 건 축이 다릅니다. 서로 다른 봇이 같은 결함 클래스에 각각 한 번씩 빠집니다. 한 봇만 떼어놓고 보면 전부 "첫 실수"입니다. 첫 실수에는 보통 검사를 안 박습니다 — 과잉이니까. 그래서 같은 자리가 다른 봇에서 N번 비어 있게 됩니다. 재발의 축이 공간입니다.

N개 봇의 1회씩이 모이면, 그것도 패턴입니다. 한 곳만 보면 영영 보이지 않는 패턴.

처방도 달라집니다. 시간축 재발은 한 레포 안의 hook으로 막습니다. 공간축 재발은 hook으로 막을 수 없습니다. 한 곳에서 배운 fix를 나머지 전부로 횡적으로 이식해야 합니다. 결함 클래스 체크리스트를 만들어 모든 봇에 한 번씩 통과시키는 것 — 그게 공간축 재발의 처방입니다.

왜 개인 프로젝트일수록 같은 클래스에서 무너지나

이 fix들은 새로운 지식이 아닙니다. 원자적 쓰기, dry-run의 read-only 보장, per-item 격리, 결손 카운터. 회사 일에서는 진작 당연하게 박던 것들입니다. 그런데 취미 봇에는 "이 정도는 사치"라며 안 박았습니다. "나만 쓰는 건데", "어차피 잘 돌잖아" — 그 믿음이 바로 가드의 부재였습니다.

헬스체크를 6개 봇에 돌린 2주에서 가장 흔한 결함이 에러가 아니라 조용히 빠지는 경로였던 것도 같은 이유입니다. 던지는 예외가 없으니 모니터링이 못 잡고, "개인 프로젝트라 안 박은 가드"가 정확히 그 빈자리였습니다. 한 건의 실패가 전체를 무너뜨리지 않게 한 작업에서 본 원자적 쓰기와 per-item 격리도 한 레포의 이야기로 끝나지 않았습니다. 다른 봇을 열 때마다 같은 빈자리가 나왔습니다.

"개인 사이드 프로젝트 = 하드닝 면제"라는 통념이, 바로 여러 곳에서 같은 자리를 비워둔 원인이었습니다. 결함이 무관한 도메인에 분산돼 있을 뿐, 그것을 막는 습관이 동일하게 빠져 있었으니 같은 모양으로 무너지는 게 당연했습니다.

AI가 무너뜨린 것은 지식이 아니라 적용 비용

그렇다면 왜 하필 지금 여섯 곳을 한꺼번에 손봤을까요. 달라진 건 무엇이 옳은 fix인지에 대한 지식이 아닙니다. 같은 수정을 이질적인 여섯 개 봇에 옮기는 한계비용이 한 오후로 줄었다는 것입니다.

자동화는 일을 줄이지 않는다는 거버넌스 비용이 본 작업을 추월하는 쪽 — 비용이 늘어나는 이야기였습니다. 이 글은 그 거울상입니다. 하드닝을 적용하는 비용이 0에 수렴하는 쪽. 한쪽은 자동화가 만든 운영 부담을, 다른 쪽은 AI가 없앤 이식 부담을 말합니다. 둘이 맞물려야 그림이 완성됩니다 — 비용은 어떤 축에서는 늘고 어떤 축에서는 줄어듭니다.

메커니즘은 둘입니다. 하나는 같은 작업을 Claude와 Codex로 병렬로 굴려 교차 검증하는 것입니다. 한 곳에서 잡은 결함의 교정 근거가 커밋 메시지에 그대로 남아, 다음 봇에 옮길 때 검증된 템플릿이 됩니다. 다른 하나는 N개 에이전트를 같은 진단 질문으로 fan-out하는 것입니다. 같은 헬스체크 렌즈를 거의 모든 봇에 재사용하니, 같은 성숙도의 안전망을 한 번에 N곳에 입히는 게 가능해졌습니다.

무엇부터 이식하나

여러 개의 개인 자동화를 굴리는 사람이라면, 이식 순서는 이렇게 잡습니다.

  1. 원자적 쓰기 — temp 파일에 다 쓰고 rename으로 교체. 중간에 죽어도 상태 파일이 반쪽으로 남지 않습니다. 손상의 폭발 반경을 0으로 만드는 가장 싼 가드입니다.
  2. dry-run의 read-only 보장 — 미리보기 경로가 운영 상태를 건드리지 않게. 미리보기의 부작용은 진짜 실행의 버그보다 잡기 어렵습니다.
  3. per-item 격리 + 결손 카운터 — 루프 한 바퀴를 try로 감싸 한 건의 실패를 한 건으로 가두되, 빠진 개수를 세어 기대보다 적으면 경보. 격리가 실패를 숨기지 않게 하는 짝입니다.

CI 게이트 래칫이 한 레포의 검사를 시간축으로 한 칸씩 조이는 패턴이었다면, 여기서는 같은 한 칸을 N개 봇에 동시에 적용합니다. AI가 이식 비용을 낮춘 덕에 래칫을 한 줄이 아니라 병렬로 돌릴 수 있게 됐습니다. 여러 개인 봇을 굴린다면, 지금 한 봇에서 고친 가드를 오늘 나머지에 그대로 복사해보세요 — 회사 일에서 이미 알던 fix를, 취미 봇 전부에 같은 성숙도로 입히는 가장 싼 방법입니다.

관련: /essays/automation-doesnt-reduce-work, /notes/recurring-trap-to-checklist, /notes/codex-competition, /logs#2026-06-07-healthcheck-six-bots