Claude Code로 블로그 정리하기
오랜만에 블로그 저장소를 열어보니 GitHub Dependabot이 취약점 36건(critical 1건 포함)을 알려주고 있었다. Claude Code의 도움을 받아 저장소 정리와 취약점 해소 작업을 진행한 내용을 기록한다.
작업 내용 요약
CLAUDE.md생성 및README.md를 블로그 정보에 맞게 수정- 미사용 gulp 관련 파일 삭제 (npm 취약점 제거)
- Ruby gem 의존성 업데이트 (Dependabot 취약점 36건 → 0건)
- 미래 날짜 글이 사이트에서 안 보이는 문제 확인
- 기존 글(AI TOOLS)을 현재 시점 내용으로 갱신, 새 글(PaperBanana) 작성
_drafts에 방치되어 있던 임시 글 11건을 보완해서 발행하고 드래프트 정리- 발행 글 전체 검토 후 낡은 글 36건 우선순위별 업데이트 (7/15)
- 시간대 미설정으로 URL 날짜가 하루 밀리는 문제 수정 (7/15)
- 본문 테이블 스타일 정비 — 미니멀 가로선 + 지브라 + 가로 스크롤 (7/24)
- 코드블록 구문 강조 정비 — 다크 배경에 라이트 팔레트가 겹쳐 있던 문제 수정 (8/18)
- GitHub 부분 장애로 Pages 빌드 실패 시 대응 기록, json 젬 취약점 패치 (8/18)
- 코드블록·표가 페이지 폭을 밀어내는 반응형 문제 연쇄 수정 (8/24·8/29)
- 자동 발행 글의 타이틀 이미지 누락·한글 폰트 깨짐 대응 (8/29)
- 발행 글마다 “검수 로그”를 Disqus 댓글로 자동 등록 (8/30)
- 스케줄러 견고화 — 놓친 실행 자동 따라잡기, 중복 마커 제외, 강제 종료 복구 사례 (9/6)
- Gist 임베드 줄무늬 가독성 수정, SSH Bastion 다이어그램 재작성 (9/7)
- SEO 정비 — 빈 og:image 제거, 트위터 카드 확대, GSC 월간 리포트 자동화 (9/7)
- 검색 순위 하락 원인 분석 후 트래픽 상위 3개 글 콘텐츠 리프레시 (9/7)
- 접이식 “원문 근거(evidence)” 블록 도입 — 핵심 주장에 원문 인용 첨부, 검수 파이프라인 규칙화 (9/9)
- GSC 사이트맵 2년 정체 발견 — 주간 재제출 자동화, robots 차단 오탐 전수 검증 (9/9)
- 자잘한 함정 둘 —
nul예약 파일명 빌드 실패, Disqus 댓글이 옛 URL 스레드로 간 문제 (9/9) - 검수 스케줄 21시 이동 — Hermes 초안 체인과의 타이밍 충돌 해소, 작성 규칙 문서로 에이전트 간 협업 (9/10)
- 관련글 목록에 최신 글 자동 역링크 — 레이아웃 개선 + 40개 글 태그 백필 (9/10)
- 공유 버튼 리디자인 (X·페이스북·링크 복사) — 레거시 CSS 특이성 함정 또 한 번 (9/10)
- 타이틀 이미지 13편 일괄 교체 — 범용 이미지에서 주제별 사진으로, 파이프라인 규칙화 (9/11~12)
- 초안이 사례를 날조한 건 — 원문에 없는 제품 프레이밍 이식, 전면 재작성 (9/12)
- Hermes 모델 프로바이더 장애로 밀린 발행 수동 따라잡기, 주간 글 중복 논문 정리 (9/13)
- 중복 재탕 4회 끝에 URL 하드 게이트 도입 — 키워드 휴리스틱에서 결정론적 grep으로 (9/14)
- 독자 질문을 받아 글에 개념 해설 섹션 추가 (9/17)
- 게이트웨이가 200으로 위장한 실패 — 무료 티어 차단 진단과 두 글 보강 (9/19~22)
- 차트 속 숫자를 지어낸 초안 — 새 환각 유형 적발과 규칙화 (9/22)
CLAUDE.md와 README 정리
Claude Code의 /init 명령으로 저장소 구조를 분석해 CLAUDE.md를 생성했다. 빌드/실행 명령, 글 작성 규칙(front matter 템플릿, <!--more--> 구분자 등)이 정리되어 있어 이후 세션에서 바로 활용할 수 있다.
README.md는 테마(Flexible-Jekyll) 원본 내용 그대로였는데, 블로그 소개와 로컬 실행 방법, 글 작성 절차로 교체했다.
gulp 관련 파일 정리
이 블로그는 Jekyll만으로 빌드되는데, 테마에 포함되어 있던 gulp 3 기반의 gulpfile.js와 package.json이 남아 있었다. 오래된 npm 의존성이 Dependabot 경고의 원인 중 하나였으므로 두 파일을 삭제하고 _config.yml의 exclude 목록도 정리했다.
git rm gulpfile.js package.json
Ruby gem 취약점 해소
남은 경고는 전부 Gemfile.lock의 Ruby gem이었다. gh CLI로 확인해보면 다음과 같다.
gh api repos/OWNER/REPO/dependabot/alerts --paginate \
-q '.[] | select(.state=="open") | [.security_advisory.severity, .dependency.package.name] | join(" | ")'
nokogiri, rexml, faraday, activesupport, webrick, addressable, google-protobuf, concurrent-ruby 등이 취약 버전으로 잠겨 있었다.
bundle update 실행 시 Gemfile에 글로벌 source가 없어 아래와 같은 오류가 발생했다.
Could not find gem 'jekyll-sitemap' in locally installed gems.
Gemfile 상단에 source와 jekyll gem을 명시하고 다시 실행하니 정상적으로 갱신되었다.
source "https://rubygems.org"
gem "jekyll"
bundle update
bundle exec jekyll build # 빌드 정상 확인 후 push
push 후 Dependabot 알림을 다시 확인하니 열린 알림이 0건으로 정리되었다.
미래 날짜 글은 사이트에 안 보인다
이 글을 처음 올렸을 때 사이트에 보이지 않아 당황했는데, 원인은 front matter의 date를 현재보다 늦은 시각으로 적었기 때문이었다. GitHub Pages는 기본 설정(future: false)에서 미래 날짜 글을 빌드에서 조용히 제외한다. 에러도 없이 그냥 안 보이므로, 새 글의 date는 반드시 현재 시각 이전으로 적어야 한다. 이 내용은 CLAUDE.md에도 주의사항으로 추가해뒀다.
글 정리
- 기존 AI TOOLS 글을 현재 시점에 맞게 갱신했다. 글에서 소개했던 AI Toolkit이 Foundry Toolkit for VS Code로 이름이 바뀌고 Bulk Run이 Evaluation으로 통합된 내용을 반영했다.
- 문장으로 아키텍처 다이어그램을 만들어주는 서비스를 소개하는 PaperBanana 글을 새로 작성했다.
드래프트 정리
_drafts 디렉토리에 2019~2023년 사이에 쓰다 만 임시 글 19건이 방치되어 있었다. 내용이 있는 11건(Slack webhook, ffmpeg SRT, 터널링 도구, 네이버 서치어드바이저 등)은 2026년 기준으로 사실관계를 다시 확인하고 보완해서 발행했고, 제목만 있고 내용이 없던 8건은 템플릿만 남기고 삭제했다. 버전이 오래됐거나(OpenSIPS 2.4 → 4.0), 이름이 바뀌었거나(MATIC → POL), 서비스 상태가 달라진 것들이 많아 오래된 드래프트는 발행 전에 반드시 현재 상태를 다시 확인해야 한다는 것을 실감했다.
발행 글 전수 검토 및 업데이트 (7/15 추가)
드래프트 정리에 이어 발행된 글 80여 건 전체를 검토해서 업데이트가 필요한 글 36건을 우선순위별로 나눠 수정했다.
- 높음 11건: 폐기된 도구/절차 —
letsencrypt-auto→ snap 기반 certbot 재작성(3건), WSL 최신 설치법(wsl --install), SMB1 보안 경고, 종료된 서비스(TeamSQL, Pocket 등) 안내, Owncast/Janus 현재 버전 반영 - 중간 14건: EOL 환경·바뀐 UI — winget 시대 반영, Tailscale 무료 플랜(현재 Personal, 6명/디바이스 무제한), Oracle 프리티어 축소(2026-06부터 A1 2 OCPU/12GB), UA → GA4, Python 3.12의
ssl.wrap_socket제거 대응 등 - 낮음 11건: 오타·죽은 링크 수정, 유지보수 중단 도구(Vundle 등) 안내. 콘텐츠 가치가 없는 Jekyll 기본 샘플 글은 삭제
모든 글은 원본 날짜와 URL을 유지한 채 본문 상단에 [업데이트] 안내 블록을 넣고 last_modified_at만 갱신하는 방식으로 처리했다. letsencrypt-auto 폐기(3건), Vundle 중단(2건)처럼 같은 원인이 여러 글에 걸쳐 있는 경우가 많았다.
시간대 미설정으로 URL 날짜가 하루 밀리는 문제 (7/15 추가)
업데이트 반영을 확인하다가 또 하나의 함정을 발견했다. _config.yml에 timezone 설정이 없으면 GitHub Pages는 UTC로 빌드하기 때문에, 게시 시각이 오전 9시(KST) 이전인 글은 사이트 URL의 날짜가 하루 앞으로 밀린다. 예를 들어 date: 2023-11-14 01:00 +0900인 글의 실제 URL은 /2023/11/13/...이 된다. 로컬 빌드(KST)와 결과가 달라 내부 링크가 조용히 깨지는 원인이 됐다.
# _config.yml
timezone: Asia/Seoul
Windows 로컬 빌드에서는 timezone 설정 시 tzinfo-data gem이 없으면 빌드가 실패하므로 Gemfile에 함께 추가해야 한다.
gem 'tzinfo-data', platforms: [:mingw, :x64_mingw, :mswin]
timezone 설정으로 오전 9시 이전 게시 글 10건의 URL이 바뀌었기 때문에, 검색엔진이 새 주소를 빨리 색인하도록 Google Search Console과 네이버 서치어드바이저에 사이트맵을 다시 제출했다. 제출 전에 sitemap.xml이 유효한 XML인지(104개 URL), robots.txt에 사이트맵 경로가 선언되어 있는지 확인했고, Search Console에서는 “성공”으로 처리 완료를 확인했다. 참고로 예전 방식의 사이트맵 ping URL은 폐지되어 재제출은 콘솔에서 직접 해야 한다.
본문 테이블 스타일 정비 (7/24 추가)
최근 글에 비교표가 부쩍 많아졌는데, 이 테마에는 테이블 CSS가 아예 없어서 브라우저 기본 스타일(경계선 없는 밀집 표)로 표시되고 있었다. _sass/_base.scss에 본문 스코프(.wrap-content table)로 기술 블로그 스타일을 추가했다: 세로선 없는 가로선 미니멀 구성, 헤더 배경과 지브라 스트라이프, 행 호버 하이라이트, 넓은 표의 표 내부 가로 스크롤.
한 가지 삽질 기록 — 처음에 배경색을 테마 변수 기반 lighten($grey-color-light, 12%)로 지정했는데, 원본 색이 이미 밝아서 밝기 한계를 넘겨 전부 #fff(흰색)로 컴파일되어 버렸다. 로컬에서는 눈치채지 못했고 배포 후 라이브 CSS를 grep으로 검사하다 발견했다. Sass 색상 함수는 컴파일 결과를 확인하기 전까지 믿지 말 것. 명시적 색상(#f4f6f8, #f9fafb)으로 교체해 해결했다.
.wrap-content table {
display: block;
overflow-x: auto; // 넓은 표는 표 안에서만 가로 스크롤
border-collapse: collapse;
th { background-color: #f4f6f8; border-bottom: 2px solid $grey-color; }
th, td { padding: 10px 14px; border-bottom: 1px solid $grey-color-light; }
tbody tr:nth-child(even) { background-color: #f9fafb; }
tbody tr:hover { background-color: #eaf2fd; }
}
코드블록 구문 강조 정비 (8/18 추가)
테이블에 이어 이번엔 코드블록이었다. 코드블록 배경은 다크(#1d1f21)인데 Rouge 구문 강조 색상은 라이트 배경용 팔레트(키워드 #069, 연산자 #555, 문자열 어두운 빨강)가 걸려 있어서, 어두운 배경 위에서 키워드가 거의 보이지 않았다. 강조 클래스가 없는 일반 토큰은 색 지정이 아예 없어 본문의 어두운 글자색을 상속받아 배경에 묻혔다.
수정 내용:
- 315줄짜리 라이트 팔레트를 GitHub Dark 계열 팔레트 30줄로 교체 (키워드 연한 빨강, 문자열 하늘색, 함수 보라, 주석 회색 이탤릭)
.highlight에 기본 텍스트색#e6edf3지정 — 강조 안 된 토큰도 항상 밝게word-break: break-all제거 — 변수명·URL이 글자 중간에서 꺾이던 것을 가로 스크롤(overflow-x: auto)로 변경
테이블 때와 같은 함정이 반복됐다는 것도 기록해둔다: 이 테마는 SCSS 산출물이 아니라 레거시 assets/css/main.css를 로드하므로, _sass/만 고치면 사이트에 반영되지 않는다. 스타일 수정 후에는 라이브 CSS를 직접 받아 반영 여부를 확인해야 한다.
GitHub 부분 장애와 Pages 빌드 실패 (8/18 추가)
위 CSS 커밋의 Pages 빌드가 실패했는데, 로그를 보니 Jekyll에 들어가기도 전에 GitHub Actions가 빌드 액션 다운로드 단계에서 429/503으로 죽어 있었다 — githubstatus.com이 “Partial System Outage”인 상태였다. 이런 경우 저장소를 고칠 게 아니라 gh run rerun으로 재시도하면 되고, 실패한 빌드가 있어도 직전 성공 빌드가 계속 서비스되므로 사이트가 내려가지는 않는다. 장애 해소 후 재실행으로 정상 배포됐고, 라이브 CSS에 새 팔레트가 반영된 것까지 확인했다.
같은 날 Dependabot 알림 1건(json 젬 2.21.1의 ResumableParser 해제 버퍼 참조, low)도 bundle update json --conservative로 2.21.2 패치해 정리했다 — 7월의 36건 정리 이후 새로 생긴 것이라, 락파일 취약점은 한 번 정리하고 끝이 아니라 알림이 올 때마다 소소하게 갱신하는 운영 항목이라는 걸 보여준다.
폭 넘침 문제 연쇄 수정 — flex와 표 (8/29 추가)
코드블록 팔레트를 정비하고 나니 폭 문제가 연달아 드러났다. 둘 다 “overflow-x: auto를 줬는데 왜 페이지가 넓어지지?”라는 같은 증상, 다른 원인이었다.
- 데스크톱(8/24): 본문 컨테이너(
.article-page)가 flex 아이템이라 기본값이min-width: auto다. 자식(긴 코드 줄)이 줄바꿈 없이 길면 스크롤이 생기는 게 아니라 아이템 자체가 늘어나 페이지를 민다.min-width: 0+max-width: 100%로 컨테이너 폭을 잠가야 overflow-x가 비로소 동작한다. flex 레이아웃의 고전 함정. - 모바일(8/29): 4~5컬럼 표가 화면 폭을 밀어냈다. “≤768px에서 표 내부 가로 스크롤” 규칙을 예전에 만들어뒀는데, 하필 사이트가 로드하지 않는 SCSS 쪽에만 넣어놨던 것 — 테이블·코드블록에 이어 세 번째로 밟은 같은 함정이다. 로드되는 레거시 CSS의 미디어쿼리에
table { display: block; overflow-x: auto; }를 넣어 해결했다.
교훈은 하나로 수렴한다: 스타일을 고쳤으면 라이브 CSS를 직접 받아서 확인하고, 배포 후 데스크톱·모바일 양쪽에서 실제 페이지를 열어봐야 한다.
자동 발행 글의 타이틀 이미지 함정 (8/29 추가)
Hermes 초안 자동 검수 파이프라인이 발행한 글에서 두 가지 이미지 문제가 나왔다.
- 이미지 미커밋: 초안이 참조하는 새 이미지가
assets/img/에 생성돼 있었지만 발행 커밋에 포함되지 않아 사이트에서 썸네일이 깨졌다. 글 파일만 add하는 습관이 원인. - 한글 폰트 깨짐(tofu): 이미지를 열어보니 다이어그램 속 한글이 전부 □로 렌더링돼 있었다. 이미지 생성 환경에 한글 폰트가 없으면 생기는 문제로, Malgun Gothic으로 재생성해 교체했다.
파이프라인 프롬프트에 두 규칙을 추가했다: 글이 참조하는 미추적 이미지도 함께 커밋할 것, 이미지를 직접 열어 한글 렌더링을 확인하고 깨졌으면 범용 이미지로 대체할 것(재생성은 사람 몫). 자동 발행에서는 “파일이 존재한다”와 “제대로 보인다” 사이의 간극을 규칙으로 메워야 한다.
검수 로그를 댓글로 — 발행 파이프라인의 투명화 (8/30 추가)
자동 발행되는 글이 늘면서 “이 글에서 뭘 검증하고 뭘 고쳤는지”를 독자에게도 보여주고 싶어졌다. Disqus API(posts/create)로 발행 직후 검수 요약을 봇 댓글로 등록하는 단계를 파이프라인에 붙였다 — 원문 대조 결과, 정정 사항, 제거한 참조, 윤문 적용 여부를 5줄 내외로 남긴다.
구현 포인트: Disqus API 앱을 만들어 API key + Access Token을 로컬에만 두고, 파이썬 표준 라이브러리로 스레드 조회(ident → link 폴백) 후 스레드가 없으면(아무도 안 연 새 글) 직접 생성해서 댓글을 단다. 댓글 등록 실패가 발행을 막지 않도록 “실패해도 발행은 유효”를 프롬프트에 명시했다. AI가 쓰고 AI가 검수하는 블로그에서 검수 과정 자체를 공개하는 것이 독자 신뢰에 대한 최소한의 예의라고 생각한다.
스케줄러 견고화 세 가지 (9/6 추가)
한 주 운영하며 잡은 자잘한 구멍들:
- 놓친 실행 자동 따라잡기: 일요일 18시에 PC가 절전이면 주간 발행이 통째로 빠졌다. 작업 스케줄러의
StartWhenAvailable을 켜서 부팅/복귀 즉시 놓친 실행을 따라잡게 했다. - 강제 종료 복구 사례: 실제로 주간 발행이 실행 도중 프로세스 강제 종료(0xC000013A)로 끊긴 날이 있었는데, 재실행된 에이전트가 커밋 직전까지 완성돼 있던 미커밋 글을 발견하고 — 재작성 대신 재검증 후 그대로 발행했다. 부분 실패 상태를 이어받는 안전장치가 실전에서 확인된 사례.
- 중복 마커 제외: Hermes가 중복 검증용으로
_drafts/에 남기는*duplicate-skipped*.md마커 때문에 매일 빈 검수가 돌았다 — 스케줄러의 초안 감지와 프롬프트 양쪽에서 마커를 제외해 “마커만 있는 날은 claude 호출 없이 스킵”으로 정리했다.
Gist 줄무늬와 다이어그램 재작성 (9/7 추가)
- Gist 임베드 줄무늬: 7/24에 만든 본문 테이블 카드 스타일(지브라·헤더 배경)이 GitHub Gist 임베드 내부의
table에까지 번져, 코드가 [Image] 줄무늬처럼 보이는 문제가 있었다. 레거시 CSS 끝에.gist table리셋을 추가하고 Gist 전용 스타일시트의 배경을 코드블록과 같은#1d1f21계열로 통일해 해결. 전역 테이블 스타일은 임베드 위젯까지 오염시킬 수 있다는 교훈. - SSH Bastion 다이어그램: 오래된 글의 구성도 이미지를 현재 내용(클라이언트 → 게이트웨이 8022 → Bastion → 내부 호스트 2대, ProxyJump 표기)에 맞게 다시 그렸다. 💻 이모지가 tofu로 렌더링되는 환경이라 노트북 아이콘은 도형으로 직접 그렸다 — 한글 폰트에 이어 이모지도 이미지 생성의 함정 목록에 추가.
SEO 정비와 GSC 월간 리포트 자동화 (9/7 추가)
메타 태그를 점검해 두 가지를 고쳤다: 썸네일 없는 글에서 og:image가 빈 값으로 출력되던 것을 조건부 출력으로 제거했고, 트위터 카드를 summary → summary_large_image로 올렸다.
이어서 Google Search Console을 서비스 계정(읽기 전용)으로 연결하고, 매월 1일 검색 실적 리포트를 자동 생성하는 스케줄러를 추가했다 — 28일 실적(클릭·노출·CTR·평균 순위)을 직전 동기간과 비교하고 상위 쿼리/페이지·기기·사이트맵 상태까지 마크다운으로 남긴다. 수동으로 콘솔을 열어보는 대신 매달 정기 데이터가 쌓이게 됐다.
순위 하락 분석과 상위 글 리프레시 (9/7 추가)
GSC 데이터를 받아보니 최근 28일 클릭이 직전 대비 −60, 평균 순위가 11.5 → 15.1로 밀려 있었다. 원인을 파보니 새 글이 노출을 희석한 게 아니라(신규 페이지는 노출의 1% 수준), 오래된 실용 글들의 순위가 8월 중순 구글 스팸 업데이트 시점에 일제히 하락한 것이었다 — 대표적으로 PowerShell 글이 15위 → 48위, “powershell” 쿼리 노출이 반토막.
대응은 날짜만 바꾸는 눈속임이 아니라 실질 콘텐츠 리프레시: 트래픽 상위 3개 글을 현재 기준으로 전면 갱신했다.
- PowerShell 글: 5.1 → 7.x 기준 전면 갱신 — 버전 표, 프로필 경로(
Documents\PowerShell\), PS7에서 제거된 wget/curl 별칭,Get-Uptime, winget - Tailscale 글: 요금 페이지 실시간 재확인 후 무료 플랜에 포함된 Tailscale SSH·MagicDNS 보강
- ffmpeg 캠 스트리밍 글: ffmpeg 8.x·winget 설치 반영, 외부망용 SRT 스트리밍/MediaMTX 섹션 신설
효과는 GSC 반영 지연을 감안해 다음 달 자동 리포트에서 확인할 예정이다. 순위 하락에 대한 정석 대응은 결국 “그 글이 지금도 정확한가”를 다시 만드는 것뿐이다.
“원문 근거” 접이식 인용 도입 (9/9 추가)
AI가 초안을 쓰고 AI가 검수하는 블로그에서 “이 수치, 진짜 원문에 있는 거 맞아?”라는 의심은 정당하다. 검수 로그 댓글(8/30)에 이어 이번엔 본문 안에서 바로 확인하는 장치를 붙였다: 핵심 수치·주장 바로 아래 “▸ 원문 근거” 알약 버튼을 두고, 클릭하면 원문에서 그대로 복사한 문장이 말풍선풍 인용 박스로 펼쳐진다. HTML <details>라 JS가 필요 없고 모바일에서도 그대로 동작하며, 접혀 있어도 검색엔진은 인용문을 색인한다.
규칙에서 가장 중요한 한 줄은 이것이다 — 인용문은 fetch한 원문에서 복사만 허용, 기억 재구성·의역 금지. 근거 블록 자체가 환각되면 신뢰 장치가 역효과이기 때문이다. GPT-6 Astra 글(5곳)부터 AgentPerf·OCI 블록볼륨 글까지 적용했고, 일간·주간 검수 파이프라인 프롬프트에도 규칙으로 넣어 이후 발행 글에는 자동으로 붙는다.
GSC 사이트맵 2년 정체와 재제출 자동화 (9/9 추가)
“robots.txt가 Googlebot을 차단한다”는 경고를 추적하다가 더 큰 문제를 찾았다. 사이트맵 170개 URL 전체를 URL 검사 API로 전수 조사한 결과 robots 차단은 0건(오탐)이었지만, 106개 URL(62%)이 “구글이 모르는 URL”이었다. 원인은 사이트맵 자체가 아니라(200·XML 유효) 구글이 사이트맵을 2024-10-13 이후 한 번도 다시 읽지 않은 것 — 재제출해도 pending으로만 남아 있었다.
대응으로 ① 서비스 계정 권한을 전체로 올려 API로 sitemap.xml을 재제출하고, ② 구글이 더 자주 읽는 경향이 있는 Atom 피드(feed.xml)를 보조 사이트맵으로 추가 제출했으며, ③ 매주 수요일 재제출 + lastDownloaded 14일 이상 정체 시 경고를 남기는 스케줄러(gsc-sitemap-resubmit)를 붙였다. 색인 회복 여부는 월간 GSC 리포트에서 추적한다. 교훈: “차단 경고”를 들으면 robots.txt만 볼 게 아니라, 구글이 실제로 판정한 상태(URL 검사 API)와 사이트맵을 언제 마지막으로 읽었는지까지 봐야 한다.
자잘한 함정 둘 (9/9 추가)
nul예약 파일명: 저장소 루트에 어쩌다nul이라는 파일이 생겼는데(Windows 예약 장치명), Jekyll이_site/nul을 지우지 못해 빌드가 통째로 죽었다. Git Bash의rm으로 양쪽을 제거해 복구. 스케줄러의 헤드리스 빌드까지 막을 수 있던 문제라, 원인 불명의 빌드 실패 때는 예약 파일명부터 의심해볼 것.- Disqus 댓글이 옛 스레드로: 검수 댓글을 달 때 페이지 경로를 파일명 날짜로 조립했는데, 이 블로그엔 파일명 날짜와 front matter date가 다른 글이 있다(URL은 date 기준). 댓글이 존재하지 않는 옛 URL 스레드에 달려 실제 페이지에는 안 보였다. 올바른 스레드에 재게시하고, 파이프라인 규칙을 “경로는 빌드 결과/sitemap에서 확인”으로 고쳤다.
검수 스케줄 이동과 에이전트 간 작성 규칙 (9/10 추가)
초안을 쓰는 Hermes의 크론 체인이 수집 18시 → 생성 19시 → 분석 20시로 바뀌면서, 19시에 돌던 Claude 검수가 그날 초안이 나오기도 전에 실행되는 타이밍 충돌이 생겼다. 검수를 21시로 옮겨 “초안이 다 모인 뒤 검수”로 순서를 바로잡았다.
같은 날, 검수 체크리스트를 작성자 관점으로 뒤집은 WRITING-RULES.md를 Hermes 작업 폴더에 두고 Hermes의 영속 메모리에 “초안 쓰기 전에 이 파일부터 읽어라”라는 포인터를 심었다. 검수에서 반복되는 지적(미래 날짜, 수치 창작, 링크 날조, 이미지 폰트)을 발행 후 고치는 대신 작성 시점에 예방하려는 시도다. 효과는 바로 나타났다 — 다음 초안(OmniDocBench 검토 글)이 처음으로 원문 인용(evidence 블록)을 스스로 달아 왔고, 인용문도 전부 진짜였다. 반면 “이미 발행한 주제 재초안” 문제는 이틀 연속 재발해서, 중복 검사 범위를 _drafts 마커만이 아니라 _posts까지 넓히라는 규칙을 추가로 심어둔 상태다. 에이전트끼리의 협업 규칙은 한 번에 완성되지 않고, 이렇게 반려 사례를 먹이며 자란다.
관련글 목록, 옛 글에 최신 글 역링크 (9/10 추가)
새 글이 옛 글을 링크해도 옛 글은 새 글의 존재를 모른다 — 이 비대칭을 없앴다. 알고 보니 테마의 “관련글 목록”은 원래 “이 글의 related 값을 태그로 가진 모든 글”을 나열하는 구조여서, 새 글이 자기 related 값을 태그에도 넣기만 하면 옛 글에 자동으로 역링크가 생기는 것이었다. 문제는 최근 글 40개가 이 조건을 안 지키고 있었다는 것.
레이아웃을 최신순·자기 제외·최대 7개로 다듬고(원래 있던 닫는 태그 버그도 수정), 40개 글 태그를 일괄 백필하고, 파이프라인 규칙에 “related 값을 tags에도 포함”을 추가했다. 이제 7월 글을 열면 관련글 목록 맨 위에 이번 주 글이 보인다. 내부 링크가 양방향으로 엮이니 색인 문제를 겪는 요즘 SEO 관점에서도 남는 장사다.
공유 버튼 리디자인 — 그리고 세 번째 특이성 함정 (9/10 추가)
글 하단의 밋밋한 텍스트 링크(“Twitter”, “Facebook”)를 브랜드 컬러 알약 버튼으로 바꿨다. X 로고(트위터 리브랜딩 반영)와 페이스북 아이콘을 SVG로 넣고, 실용성이 가장 높은 “링크 복사” 버튼을 추가했다 — 클릭하면 클립보드 복사 후 1.5초간 “복사됨!”으로 바뀐다. 트윗 공유 URL의 한글 제목이 깨지던 버그(url_encode 누락)도 함께 수정.
교훈 코너: 배포하고 보니 버튼이 hover 전까지 안 보였다. 테마의 기존 규칙 .page-footer .page-share a(연회색 배경)가 새 규칙보다 특이성이 높아 배경을 덮었고, 글자색(흰색)만 새 규칙이 이겨서 흰 배경에 흰 글자가 된 것. 테이블·코드블록·gist에 이어 이 레거시 CSS에서만 벌써 몇 번째 밟는 함정이다. 이 파일에 뭔가 추가할 때는 기존 셀렉터보다 특이성을 높여서 쓰는 게 기본값이어야 한다.
타이틀 이미지, 범용에서 주제별로 (9/12 추가)
자동 발행 글들이 죄다 같은 이미지(command-title.webp)를 달고 있었다. 초안 템플릿의 기본값을 아무도 안 바꿔서다. 목록 화면에서 글이 구분되지 않는 건 물론이고, 공유했을 때 썸네일이 전부 같으니 클릭할 이유가 없다.
9월 글 8편과 8월 글 5편, 모두 13편의 타이틀 이미지를 주제에 맞는 Unsplash 사진으로 바꿨다 — 나비에-스토크스 글에는 유체 소용돌이, RTX 로컬 LLM 글에는 GeForce RTX 각인이 보이는 그래픽카드, 와트당 성능 글에는 석양의 송전탑, 문서 파싱 벤치마크 글에는 뒤섞인 서류 더미. 주간 연구 동향 시리즈만은 통일 이미지를 유지했다. 시리즈물에서는 같은 이미지가 오히려 정체성이기 때문이다.
절차는 파이프라인 규칙으로 굳혔다: 범용 이미지가 걸려 있으면 ① 주제 키워드로 Unsplash 검색 → ② 다운로드 → ③ 이미지를 직접 열어 적합성 확인 → ④ 1200×675 webp로 크롭 → ⑤ 출처 링크 추가 → ⑥ 글과 함께 커밋. ③이 중요하다. 실제로 오늘도 첫 후보(낡은 흑백 울타리 사진)가 주제와 안 맞아 다시 받았다. 파일이 존재하는 것과 쓸 만한 것은 다르다 — 8월에 한글 폰트 깨짐으로 배운 교훈의 반복이다.
초안이 사례를 날조했을 때 (9/12 추가)
지금까지 겪은 초안 오염 중 가장 심한 건이 나왔다. AWS 기술 블로그의 삼성계정 AIOps 사례를 요약한 초안이었는데, 원문에 한 번도 등장하지 않는 “AgentCore 기반”이라는 프레이밍을 통째로 이식하고 그 위에 3계층·6도메인·4필라 아키텍처와 코드 스니펫까지 지어냈다. 실제 원문의 스택은 Bedrock + Strands Agents SDK + FastMCP였다. 여기에 삼성 팀의 1인칭 서술(“우리는…”)을 그대로 복사하고, 일본어 문장까지 섞여 있었다.
링크는 멀쩡했다. 링크만 보고 넘어갔다면 그대로 나갈 뻔했다 — “링크가 살아있다”와 “본문이 정확하다”는 완전히 다른 문제라는 걸 또 확인한 셈이다. 원문을 받아 전문 대조한 뒤 패치가 아니라 전면 재작성으로 처리했고, 결과물은 오히려 초안보다 나아졌다(오픈소스 MCP 서버의 한계 → 커스텀 MCP → 단일 에이전트의 검증 불가 → Agents as Tools로 이어지는 주차별 여정, us-east-1 장애를 3분 47초에 분석한 실전 기록).
Hermes 메모리에는 이 사례를 그대로 박아뒀다: “원문에 없는 제품·프레임워크를 사례에 이식하지 말 것, 원문의 1인칭을 복사하지 말 것, source_url 필수.”
크론이 멈춘 날 (9/13 추가)
Hermes의 초안 생성 크론이 “모델 프로바이더에 연결할 수 없다”는 오류로 실패했다. 일시적 장애였지만 여파는 체인 전체로 번졌다 — 초안이 21:45에야 도착해 21시 검수가 이미 끝난 뒤였고, 주간 보고서도 늦어져 20시 주간 발행이 “보고서 없음”으로 스킵됐다.
다행히 설계된 안전장치는 전부 제대로 동작했다. 검수는 초안이 없으니 claude를 호출하지도 않고 종료했고, 주간 발행은 조건 미달로 커밋 없이 빠졌다. 실패했을 때 아무것도 망가뜨리지 않는 것이 무인 파이프라인의 최소 요건인데 그건 지켜진 셈이다. 놓친 작업은 세션에서 수동으로 따라잡았다: 늦게 도착한 LINE Tech-Verse 참관기 글을 검수·발행하고, 주간 발행 스크립트를 손으로 재실행했다.
덤으로 주간 글에서 문제 하나를 더 찾았다. 수집 기간 밖(직전 주) 논문이 섞여 들어와 “(창 직전)”이라는 정체불명의 라벨이 붙어 있었고, 지난주 글과 겹치는 논문도 한 편 있었다. 라벨을 걷어내고 중복 논문을 빼면서, 주간 발행 프롬프트에 “직전 주 글의 arXiv ID와 대조해 겹치면 제외”를 규칙으로 넣었다.
중복 재탕 4회, 결국 하드 게이트 (9/14 추가)
같은 원문으로 이미 발행한 글을 Hermes가 다시 초안으로 써오는 일이 엿새 사이 네 번 반복됐다. RTX 로컬 LLM 가이드, GPT-6 Astra 두 번, 그리고 어제 발행한 LINE Tech-Verse 참관기를 오늘 또. 그때마다 검수에서 걸러내고 Hermes 메모리에 “발행 글과 중복 확인하라”는 규칙을 심었는데, 규칙을 심을수록 안 지켜진다는 사실만 확인했다.
원인을 파보니 명확했다. 초안 생성 스킬의 중복 검사가 “제목·키워드 3개 이상 일치” 같은 휴리스틱뿐이었다. 모호한 기준을 모델 판단에 맡긴 것이다. 어제 발행한 글조차 못 걸러낸 이유가 여기 있다.
그래서 판단을 없앴다. 후보를 고른 직후, 본문을 읽기도 전에 원문 URL을 _posts/에 grep하는 단계를 맨 앞에 넣었다. 한 줄이라도 걸리면 무조건 스킵이다. 발행 글은 본문에 원문 링크를 반드시 포함하므로 이 검사는 사실상 100% 잡는다. 기간 제한도 없앴다 — 같은 URL이면 1년 전 발행이어도 중복이다. 스킬 문서와 크론 지시문 양쪽에 같은 내용을 박아 한쪽을 건너뛰어도 걸리게 했다.
공교롭게도 이날 발행한 LINE Tech-Verse 참관기가 말하는 하드 게이트가 정확히 이것이다. 경고가 아니라 차단, 그리고 “모델이 괜찮다고 했는가”가 아니라 “grep 결과가 비었는가“로 기준을 옮기는 것. 규칙을 문서로 늘리는 것과 실행 경로에 게이트를 박는 것은 전혀 다른 일이라는 걸, 네 번 당하고서야 몸으로 배웠다.
독자 질문이 글을 고친다 (9/17 추가)
「능력 세탁」 글을 발행한 다음 날 “제목이 무슨 뜻인지 모르겠다”는 말을 들었다. 설명을 해보니 그 설명이 글보다 나았다. 원래 원문 구조를 정확히 옮기는 데 집중했는데, 정작 왜 이게 위험한지를 잡아주는 비유가 빠져 있었던 것이다.
그래서 해설 섹션을 새로 넣었다. 돈세탁 구조(쪼개 입금하면 각 창구엔 정상 거래 한 건)로 이름의 유래를 잡고, 월·수·금 다른 조교에게 무해한 질문을 나눠 묻고 집에서 답을 조립하는 시험 과제 비유로 공격 구조를 풀었다. 요약 글에서 자주 놓치는 건 사실이 아니라 독자가 발 디딜 첫 계단이라는 걸 다시 배웠다. 원문 대조는 정확성을 지켜주지만 이해까지 만들어주지는 않는다.
조용히 실패하는 게 제일 무섭다 (9/19~22 추가)
에이전트가 쓰던 모델이 어느 날부터 이상해졌다. 게이트웨이에 직접 물어보니 원인이 나왔다.
[403] OpenCode's free tier can only be used from within OpenCode
무료 티어를 자사 클라이언트 안에서만 쓰도록 막아버린 것이다. 문제는 이 실패가 실패처럼 보이지 않는다는 점이었다. 라우팅이 남은 후보로 조용히 폴백해서, 추론 모델을 부르는 별칭(auto/best-reasoning)이 경량 모델로 떨어지는데도 요청은 200으로 성공한다. 로그 어디에도 에러가 없고 품질만 슬그머니 내려간다. 무료 모델을 묶어 쓰는 구성에서는 “호출이 성공했는가”가 아니라 응답의 model 필드가 무엇인가를 봐야 한다는 걸 이때 배웠다.
파보니 더 큰 전제가 흔들렸다. “월 16.2억 무료 토큰”은 게이트웨이가 주는 게 아니라 사용자가 각 서비스에 가입해 받은 할당량의 총합이고, 가입·키 없이 쓰는 통로는 키리스로 내장된 OpenCode Free 하나뿐이었다. 그 하나가 막히자 무료 전용 라우팅(auto/best-free)이 502로 통째로 깨졌다. 두 글(에이전트 스택, OmniRoute 셀프호스팅)에 실측과 함께 나눠 담았다 — 앞 글은 “라우팅이 어떻게 조용히 망가지는가”, 뒤 글은 “무료의 조건은 무엇인가”.
교훈은 하나로 모인다. 무료 티어 위에 쌓은 스택의 가용성은 남의 정책 변경 한 줄에 달려 있다. 그리고 그 변경은 보통 공지 없이 온다.
차트 속 숫자를 지어낸 초안 (9/22 추가)
새로운 유형의 환각을 만났다. Hugging Face의 transformers GGUF 지원 글을 요약한 초안이었는데, 벤치마크 표가 통째로 창작이었다.
원문을 열어보니 수치가 본문 텍스트에 없었다. 차트 이미지로만 있었다. 초안은 그 빈자리를 그럴듯한 숫자로 채웠다 — 모델명(Qwen3.5-32B ← 실제 Qwen3.8-27B), tok/s 값, 그리고 “transformers가 llama.cpp의 90~92% 수준”이라는 결론까지. 차트 SVG를 받아 <text> 라벨을 뽑아보니 실제로는 27B에서 transformers가 더 빨랐다(15.9 대 13.4). 수치만 틀린 게 아니라 결론 방향이 반대였던 것이다.
지금까지의 검수 규칙은 “원문에 없는 수치를 만들지 말 것”이었는데, 이번 건은 그 규칙의 사각지대였다. 원문에 있긴 한데 텍스트가 아니라 그림 안에 있었으니까. 그래서 규칙을 이렇게 고쳤다.
차트 안에만 있는 수치는 SVG를 받아
<text>라벨로 확인하거나, 못 읽으면 수치를 빼고 “차트로만 제시됨”이라고 쓴다. 읽지 못한 숫자로 결론 방향을 정하는 것은 금지.
링크는 살아 있었고, 커널 이름 5종·PR 번호·제약사항·코드 예시까지 전부 정확했다. 그래서 더 위험했다. 검증 가능한 부분이 다 맞으면 나머지도 맞겠거니 하게 되는데, 환각은 정확히 검증하기 번거로운 곳에 숨는다.
마무리
테마를 fork해서 쓰는 블로그는 사용하지 않는 빌드 도구 흔적이 남아 취약점 경고의 원인이 되기 쉽다. 실제로 쓰는 의존성만 남기고, Gemfile.lock은 주기적으로 bundle update로 갱신해주는 것이 좋겠다. 드래프트도 쌓아두면 결국 내용이 낡아버리니 그때그때 발행하거나 정리하는 편이 낫다. 그리고 미래 날짜 글 미노출, 시간대에 따른 URL 밀림처럼 GitHub Pages의 조용한 함정들은 한 번 겪을 때마다 CLAUDE.md에 기록해두면 같은 실수를 반복하지 않는다.