shud.log
바이브코딩 기록

파수꾼이 6일 동안 안 짖었는데, 기록은 전부 정상이었다

2026-08-04바운티ZeroClaw삽질기록텔레그램

이 글은 개인 개발 기록이며 투자 조언이 아닙니다. 저는 라이선스 있는 투자 자문가가 아닙니다. 아래에 나오는 지갑은 제 실제 지갑이고, 금액은 백 달러가 안 되는 소액입니다. 판단과 책임은 각자에게 있습니다.

초록 램프를 켜둔 채 웅크리고 자는 도트 파수꾼 로봇

솔라나에 Kamino라는 대출 서비스가 있다. 코인을 맡기고 돈을 빌리는 곳인데, 담보 가치가 떨어져서 어떤 선을 넘으면 강제로 청산당한다. 그 선까지 얼마나 남았는지를 매일 아침 텔레그램으로 알려주는 봇을 만들었다.

지난 글에서 부품이 꽂혔다는 얘기까지 썼다. 그 다음 날 밤에 알림까지 붙였고, 일회성으로 한 번 쏴보니 폰이 울렸다. 됐다 싶었다.

그리고 일주일 동안 안 들여다봤다.

초록불을 믿었다

7월 28일에 확인해봤다. 스케줄러 기록은 이랬다.

2026-07-27  08:00  ok
2026-07-26  08:00  ok
2026-07-25  08:00  ok
...

매일 제 시간에 실행됐고 전부 성공이다. 리포트 내용도 정상이었다. 그래서 나는 이게 잘 돌아가는 줄 알았다.

그런데 텔레그램 API에 직접 물어보니 그 대화방의 마지막 메시지 번호가 3이었다. 메시지 번호는 하나 보낼 때마다 1씩 올라간다. 매일 아침 보냈으면 최소 6은 넘었어야 한다.

3이라는 건, 여섯 밤 동안 폰에 아무것도 안 왔다는 뜻이다.

웃긴 건 내가 그걸 몰랐다는 거다. 알림이 안 오면 이상하다고 느꼈어야 하는데, 기록에 초록불이 찍혀 있으니까 폰을 안 봤다. 도구를 믿은 게 아니라 도구의 자기 보고를 믿은 거다.

로그 안쪽에서는 매일 실패하고 있었다

실행 로그를 파보니 이 줄이 매일 있었다.

Channel 'telegram.default' not found. Available: []

채널이 없다는 소리다. 그런데 채널은 멀쩡했다. 진단 명령을 돌리면 healthy가 나오고, 손으로 보내면 보내지고, 텔레그램 API에 직접 쏘면 도착한다.

원인은 이랬다. 스케줄러가 시간 맞춰 AI 에이전트를 깨우는데, 그렇게 깨어난 에이전트한테는 채널 목록이 안 붙는다. 그래서 리포트는 정상으로 만들어놓고 마지막 전송 단계에서 조용히 실패한다. 그리고 스케줄러 입장에서는 시킨 일이 끝났으니 ok로 적는다.

여기가 이 사고의 핵심이다. 스케줄러가 기록하는 ok는 "실행이 끝났다"는 뜻이지 "결과가 도착했다"는 뜻이 아니었다. 나는 두 개를 같은 말로 읽고 있었다.

뻔한 해법은 안 통했다

제일 먼저 한 건 설정 파일에서 에이전트한테 채널을 직접 물려주는 거였다. 채널이 없다니까 붙여주면 되는 거 아닌가.

안 됐다. 일회성 예약을 걸어서 다시 확인했는데도 Available: []가 그대로였다. 그 연결은 사람이 명령어를 칠 때 쓰는 통로에만 걸리고, 스케줄러가 깨우는 쪽에는 안 걸리기 때문이다.

그래서 방향을 바꿨다. 에이전트한테 전송까지 시키지 말고, 두 단계로 쪼개기로 했다. 리포트를 만드는 명령과 텔레그램으로 보내는 명령을 따로 부르는 짧은 파이썬 스크립트를 하나 만들었다. 둘 다 채널이 붙는 통로다.

셸 스크립트가 아니라 파이썬인 데는 이유가 있다. 이 프로그램의 기본 안전 설정이 예약 작업에서 python3은 허용하고 sh는 막아뒀다. 막아둔 걸 푸는 것보다 허용된 걸 쓰는 게 맞다고 봤다.

침묵도 오답이고 초록불도 오답이다

고치면서 규칙을 하나 박아넣었다. 이게 이 스크립트에서 제일 중요한 부분이다.

실행이 실패하거나, 시간을 초과하거나, 돌아온 텍스트에 제대로 된 판정이 안 들어 있으면, 그냥 조용히 넘어가지 않는다. 대신 이런 알림을 보내고 종료 코드 1로 죽는다.

[UNKNOWN] 포지션을 읽지 못했습니다.
이것은 "포지션 없음"이 아닙니다. 직접 확인하세요.

파수꾼한테 제일 나쁜 실패는 못 짖는 게 아니다. 못 봤으면서 이상 없다고 하는 것이다. 담보가 위험한데 조회가 안 된 상황과, 조회는 잘 됐는데 빌린 돈이 없는 상황은 화면에 똑같이 "특별한 일 없음"으로 보일 수 있다. 이 둘을 헷갈리면 청산당하고 나서야 알게 된다.

그래서 모르면 모른다고 크게 말하게 했다. 그리고 예약 기록에도 실패로 남게 종료 코드를 1로 뒀다.

검증은 스케줄러를 통해서 했다. 손으로 돌려서 되는 건 이미 알고 있었고, 애초에 문제가 손으로 할 때만 되는 거였으니까. 일회성 예약을 걸었더니 메시지 번호가 9로 하나 올라갔고, 그 다음 번호가 10이 됐다. 이번엔 진짜 도착한 거다.

덤으로 하나 더 알게 됐다. 그날 실행이 08시 08분에 시작해서 2시간 17분 걸려서 끝났다. 맥북이 중간에 잤기 때문이다. 다음 날 집에 있는 맥미니로 통째로 옮겼다. 파수꾼을 잠드는 기계 위에 올려두면 안 된다는 걸 배웠다.

사흘 뒤, 같은 버그를 내 수정 안에서 찾았다

여기까지가 7월 28일이다. 이 글이 여기서 끝났으면 그냥 흔한 삽질기다.

빨간 경고 문서에 초록 도장을 찍는 도트 로봇

7월 31일에 제출 자료를 정리하다가, 방금 만든 그 파이썬 스크립트를 다시 읽었다.

스크립트는 돌아온 텍스트가 진짜 리포트인지 아닌지를 판단해야 한다. 리포트라면 보내고, 아니면 아까 그 UNKNOWN 알림을 보내야 하니까. 판단 방법은 이랬다. 텍스트 안에 [OK], [WARN], [DANGER], [NO-POSITION] 중 하나가 들어 있으면 리포트로 친다.

그런데 조회에 실패했을 때 플러그인이 뱉는 문장이 이거다.

[UNKNOWN] Kamino sentinel could not read the position.
This is NOT a NO-POSITION result. Check manually.

NOT a NO-POSITION.

"이건 포지션 없음이 아니다"라고 말하는 그 문장 안에 NO-POSITION이라는 글자가 들어 있다. 내 코드는 텍스트 어디에든 그 글자가 있으면 리포트라고 판단했다. 그래서 조회 실패 메시지가 오늘의 정상 리포트로 폰에 배달되고, 종료 코드 0으로 깨끗하게 끝났다. 스케줄러에는 초록불이 찍혔다.

머리로 추측한 게 아니라 실제로 재현했다. 있지도 않은 지갑 주소를 넣고 실제 서버에서 돌려서, 실행 기록에 남은 원문으로 확인했다.

이게 아픈 이유

이 프로젝트가 하려는 말이 딱 하나다. 초록불은 증거가 아니다.

조회 실패와 포지션 없음을 구분하라고 만든 도구다. 그 구분을 못 하면 파수꾼 자격이 없다고 README에 써놨다. 실패하면 크게 짖으라고 규칙까지 박아뒀다.

그러고서 그 규칙을 어기는 코드를 6일치 배포했다. 그것도 같은 착각을 두 번, 두 번째는 첫 번째를 고치는 수정 안에서.

규칙을 적어놨다고 그 규칙에서 면제되는 게 아니었다. 오히려 적어놨기 때문에 다 지킨 줄 알았다.

고친 방법

두 가지를 바꿨다.

판정 인식을 첫 줄 맨 앞에 붙은 대괄호 태그로 못박았다. 리포트를 찍어내는 함수가 원래 모든 판정을 그렇게 렌더링한다. 그러니까 첫 글자가 [OK]로 시작하지 않으면 리포트가 아니다. 문장 중간에 판정 이름이 나오는 건 이제 안 걸린다. 판정이 아니라 판정을 부정하는 문장이면 더더욱.

그리고 실행 기록에서 도구 호출이 실패했다는 표시가 붙은 항목은 아예 건너뛰게 했다. 텍스트를 읽어서 짐작할 게 아니라, 실패했다는 신호가 이미 데이터에 있었다. 내가 안 보고 있었을 뿐이다.

마지막으로 회귀 테스트를 붙였다. 실패 케이스의 입력값은 방금 그 실제 실행 기록 문자열을 그대로 복사해서 넣었다. 지어낸 예시로 테스트하면 지어낸 것만 잡는다.

python 테스트 20개 통과
cargo test 10개 통과

오늘 상태

run_kst              status  duration_ms
-------------------  ------  -----------
2026-08-04 08:00:02  ok      31906
2026-08-03 08:00:02  ok      33337
2026-08-02 08:00:02  ok      35709
2026-08-01 08:00:02  ok      34188
2026-07-31 08:00:01  ok      36695
2026-07-30 08:00:02  ok      32827

여섯 줄이고 전부 ok다. 이번엔 폰에도 여섯 개가 와 있다. 그걸 확인하고 나서야 이 표를 믿는다.

7월에도 똑같이 생긴 표를 봤었다. 표는 그때도 지금도 거짓말을 안 했다. "실행됐다"고만 말했고, 그 이상을 읽은 건 나였다.

남은 것

이 봇은 지금 참가 중인 바운티 제출물이다. 마감이 사흘 남았고, 남은 건 3분짜리 시연 영상이다.

영상에 이 실패 장면을 넣기로 했다. 잘 되는 것만 보여주는 게 나을 것 같기도 한데, 남들도 다 그렇게 찍을 거다. 그리고 이 프로젝트에서 제일 할 말이 있는 화면은 초록불이 아니라 [UNKNOWN]과 종료 코드 1이 같이 찍힌 화면이다.

사실 오늘 아침에 그 영상을 찍다가 접었다. 여덟 컷 중에 몇 개 찍고 나니까 하기 싫어져서 녹화를 껐다. 마감은 사흘 남았으니까 내일 다시 켤 거다.

이 글은 개인 개발 기록입니다. 특정 서비스나 토큰의 홍보, 투자 권유가 아닙니다.

이어서 읽기
← 목록으로