핸드폰 하나로 서버 장애를 해결한 기록
텔레그램 봇이 멈췄다
나는 개인 프로젝트로 텔레그램 봇을 하나 운영하고 있다. 증권사 리포트 PDF를 자동으로 수집해서 AI로 요약한 뒤 채널에 발송하는 봇이다. 구독자가 약 2,000명 가까이 되는, 나름 의미있는 사이드 프로젝트다.
그런데 며칠 전부터 이상한 점을 발견했다. PDF 파일은 정상적으로 다운로드되어 채널에 올라가는데, 요약본이 사라졌다. 한화투자증권, POSCO홀딩스, 하나증권 — 리포트는 쌓이는데, 어디에도 AI 요약이 없다.
문제는 타이밍이었다. 이걸 발견한 건 컴퓨터 앞이 아니라 핸드폰을 보고 있을 때였다.
-----
핸드폰에서 Claude Code를 열다
예전이라면 컴퓨터 앞에 앉아야 디버깅을 시작할 수 있었다. 하지만 지금은 다르다. 모바일에서 Claude Code를 열고 서버에 접속했다.
“여전히 요약본이 안나오고 있음”이라고 한 줄 쳤다.
Claude Code는 먼저 몇 가지를 물어봤다. 어떤 요약본인지, 이전에 설정한 것이 있는지, 어떤 스크립트를 실행하고 있는지. 맥락을 파악하려는 것이다. 작업 디렉토리를 확인하고 나서, 본격적인 탐정 작업이 시작됐다.
-----
AI 탐정의 수사 과정
Claude Code가 한 일을 순서대로 정리하면 이렇다.
1단계: 현장 확인 — 오늘 날짜(03/26) 폴더를 확인했다. PDF 다운로드는 정상. 요약 생성 관련 코드를 읽기 시작했다.
2단계: DB 조회 — SQLite DB의 `sent_reports` 테이블을 직접 쿼리했다. 여기서 첫 번째 단서가 나왔다. 3/25부터 요약이 전혀 생성되지 않고 있었다. 3/23도 마찬가지. `sent=1`인데 `summary_text`가 비어 있다. 요약 없이 “전송 완료”로 처리되고 있었다는 뜻이다.
3단계: 로그 추적 — 로그를 뒤졌더니, 모든 리포트가 “이미 처리됨”으로 스킵되고 있었다. 다른 스크립트가 요약 없이 `sent=1`로 저장한 것이다.
4단계: 컬럼 분석 — DB 데이터를 자세히 들여다보니, `sent` 컬럼에 타이틀 텍스트가 들어가 있었다. 컬럼이 뒤섞여 있었다. 여기서 두 번째 단서.
-----
원인은 두 개였다
Claude Code가 최종적으로 찾아낸 원인은 두 가지였다.
문제 1: 실행 중인 스케줄러가 옛날 코드를 사용 — `report_scheduler.py`는 시작할 때 `importlib`로 모듈을 한 번만 로드한다. `summarize_scraped_content` 함수에서 OpenAI API 호출 시 `max_tokens` 파라미터를 사용하고 있었는데, `gpt-5-nano` 모델은 이걸 지원하지 않고 `max_completion_tokens`만 지원한다. 코드 파일은 이미 수정되어 있었지만, 스케줄러를 재시작하지 않아서 메모리에 올라간 옛 코드가 계속 실행되고 있었다. 결과: OpenAI API 400 에러.
문제 2: 요약 실패인데 `sent=1`로 저장 — 요약이 실패했는데도 DB에 `sent=1`로 기록되어, 다음 실행 시 “이미 처리됨”으로 스킵되는 악순환이 발생했다.
한 마디로, 코드는 고쳤는데 프로세스를 재시작 안 한 것 + 에러 핸들링 미비의 합작이었다. 개발자라면 한 번쯤 겪어봤을 클래식한 실수다.
-----
수정도 Claude Code가 했다
원인을 찾은 뒤, Claude Code는 수정 계획을 제시했다.
조치 1:DB에서 요약 없이 `sent=1`로 저장된 294개 레코드를 `sent=0`으로 리셋.
조치 2: 스케줄러를 재시작하여 수정된 코드를 적용.
“부탁해”라고 한 마디 치니, 직접 SQL을 실행하고 스케줄러를 재시작했다. 이어서 수동 테스트까지 돌려 요약이 정상 생성되는 것을 확인했다. API 호출도 200 OK.
전 과정이 핸드폰 화면 안에서 이루어졌다. 컴퓨터는 건드리지도 않았다.
-----
이것이 ‘디지털 군단’의 실체다
이 에피소드가 중요한 이유는, 지난 일주일간 이야기해 온 개념들이 실제로 작동하는 장면이기 때문이다.
에이전틱 루프 — Claude Code가 파일을 읽고, DB를 쿼리하고, 로그를 분석하고, 원인을 특정하고, 코드를 수정하고, 테스트까지 돌렸다. 사람은 중간중간 승인 버튼을 누른 것뿐이다.
모바일 원격 제어 — Dispatch나 Claude Code 원격 기능이 아니어도, 모바일 Claude Code만으로 서버 장애를 진단하고 해결할 수 있었다. “컴퓨터 앞에 앉아야 한다”는 마지막 제약이 사라지고 있다.
자연어 디버깅 — “요약본이 안나와”라는 한 줄에서 시작해서, `max_tokens` vs `max_completion_tokens` 파라미터 차이까지 도달했다. 프로그래밍 언어의 문법을 외울 필요가 없다.
이전 글에서 “생각은 GPT-5.4처럼 깊게, 행동은 Claude Code처럼 빠르게”라고 썼다. 오늘 그 문장이 핸드폰 화면 안에서 증명됐다.
-----
설계자의 교훈
이번 경험에서 얻은 교훈은 기술적인 것보다 구조적인 것이다.
에이전트가 아무리 똑똑해도, 프로세스 재시작은 사람이 판단해야 한다. 코드를 고치는 것과 실행 환경에 반영하는 것은 별개의 문제다. 이 간극을 메우는 것 — CI/CD 파이프라인, 헬스체크, 자동 롤링 리스타트 — 이 바로 ‘설계자’의 몫이다.
에이전트는 점점 더 많은 것을 자율적으로 할 수 있게 될 것이다. 하지만 그 자율성이 올바르게 작동하려면, 그 아래에 사람이 설계한 구조가 있어야 한다.
당신의 디지털 군단은 오늘 밤, 어떤 문제를 해결하고 있는가?