본문 바로가기
개발/사이드프로젝트

매일 새벽 5시, AI가 내 사이트에 PR을 보낸다 — 클로드로 콘텐츠 공장 만들기

by beomcoder 2026. 7. 20.
728x90
반응형

내가 운영하는 놀랩(nolab.kr)은 심심풀이 테스트·퀴즈·게임을 모아둔 사이트다. 콘텐츠를 계속 새로 올려야 사는 사이트인데, 매일 소재를 찾고 만드는 게 일이었다. 그래서 이걸 통째로 자동화했다. 지금은 이렇게 돌아간다:

매일 새벽 5시, 클로드 클라우드 에이전트가 알아서 요즘 유행을 조사하고, 콘텐츠 2~3개를 코드로 만들어 GitHub PR로 보낸다. 나는 아침에 폰으로 PR을 열어 실제 동작하는 프리뷰를 눌러보고 — Merge(배포) / 코멘트(수정 요청) / Close(폐기) 중 하나를 누른다. 끝.

 

이 글은 그 파이프라인을 만든 전체 과정이다. 뭘 준비해야 하는지, 어떤 순서로 만들었는지, 클로드에게 실제로 어떤 프롬프트를 줬는지, 수정·커밋·스케줄링이 어떻게 굴러가는지 전부 적었다.

0. 전체 그림 — 어드민 페이지를 만들지 마라

처음엔 "어드민 페이지를 만들어서, AI가 만든 콘텐츠를 보여주고, 승인 버튼을 누르면 배포되게 하자"고 생각했다. 그런데 설계하다 보니 그 어드민 페이지가 이미 존재했다. GitHub의 PR 화면이다.

이 구조의 좋은 점:

  • 승인 UI를 한 줄도 안 만들었다. GitHub 모바일 앱이 그대로 대시보드다.
  • 프리뷰가 공짜다. Vercel(또는 Netlify)은 PR을 열면 실제 동작하는 프리뷰 URL을 자동으로 달아준다. 스크린샷이 아니라 진짜로 눌러보고 판단할 수 있다.
  • 수정·폐기가 Git의 기본 동작이다. 수정 = 브랜치에 커밋 추가, 폐기 = PR 닫기. 롤백도 Git이 해준다.
  • main은 항상 안전하다. AI는 main에 직접 푸시할 수 없고, 사람이 Merge를 눌러야만 배포된다.

1. 준비물

항목내용

Claude 구독 Pro 또는 Max. API 종량제가 아니라 구독만으로 로컬 Claude Code + 클라우드 스케줄 에이전트를 전부 쓴다.
Claude Code 로컬 개발·파이프라인 구축용 CLI/IDE 확장. 스케줄 등록도 여기서 /schedule로 한다.
GitHub 저장소 사이트 코드가 GitHub에 있어야 한다. 클라우드 에이전트는 GitHub 저장소를 클론해서 작업한다.
Vercel 연동 main 푸시 = 배포, PR = 프리뷰 배포가 되도록 GitHub 연동. (Netlify 등도 동일)
GitHub 연결 2종 claude.ai ↔ GitHub 계정 연결(OAuth) + Claude GitHub App을 저장소에 설치. 아래 3단계에서 자세히.

2. 1단계 — 파이프라인을 손으로 한 바퀴 돌려본다

스케줄부터 걸면 안 된다. 먼저 "브랜치 → PR → 프리뷰 → 승인" 한 사이클을 로컬 클로드와 함께 수동으로 돌려서, 이 흐름이 우리 저장소에서 실제로 매끄러운지 확인했다.

 

1호 콘텐츠는 "90년대 노래 이상형 월드컵"이었다. 이 소재를 고른 이유가 자동화 관점에서 중요하다:

  • 저작권 안전 — 음원을 서버에 올리는 게 아니라 유튜브 공식 임베드(iframe)로 재생한다. 영상과 조회수는 원저작자 몫. 연예인 사진을 긁어오는 류의 콘텐츠는 초상권·저작권 리스크가 커서 처음부터 금지 목록에 넣었다.
  • 기존 엔진 재사용 — 이미 있던 치킨 이상형 월드컵의 토너먼트 코드를 복사해 카드만 유튜브 플레이어로 바꿨다. AI에게 "기존 패턴을 따라라"라고 시킬 수 있는 구조가 자동화의 핵심이다.
  • 데이터 검증이 자동화 가능 — 유튜브 video ID를 AI 기억으로 쓰면 반드시 틀린다. 실제 검색으로 찾고, 곡마다 oEmbed API로 임베드 허용 여부를 검증시켰다:
    curl -s -o /dev/null -w "%{http_code}" \
      "https://www.youtube.com/oembed?format=json&url=https://www.youtube.com/watch?v=VIDEO_ID"
    # 200 = 임베드 가능성 높음 / 401·403 = 임베드 차단 / 404 = 영상 없음
    16곡 전부 200을 받은 것만 데이터에 넣었다. 음반사 공식 MV 중엔 외부 임베드를 막아둔 게 있어서 이 검증이 필수다.

제작이 끝나면 로컬에서 품질 게이트를 통과시킨다(우리 저장소의 CLAUDE.md에 규칙으로 박아둔 것): SEO 메타·sitemap 등록, 사이트 체인지로그 기록, tsc 타입체크, 프로덕션 빌드, 헤드리스 브라우저(playwright)로 전체 플로우 완주 확인. 그리고:

git switch -c feat/song90-worldcup
git add … && git commit -m "90년대 노래 이상형 월드컵 신설 …"
git push -u origin feat/song90-worldcup
gh pr create --title "🎤 90년대 노래 이상형 월드컵" --body "…승인 방법: Merge=배포 / 코멘트=수정 / Close=폐기"

 

 

몇 분 뒤 PR에 Vercel 프리뷰 링크가 자동으로 달렸고, 폰에서 눌러보고 Merge를 눌렀더니 2~3분 만에 실서비스에 반영됐다. 파이프라인 검증 끝.

3. 2단계 — 수정 루프를 경험해 본다

머지 전에 PR에 이렇게 코멘트를 남겨봤다:

"처음부터 시작하니까 노래가 바로 안 나오는데, 하이라이트 부분이나 노래가 시작하는 부분으로 위치하게 할 수 있을까?"

 

클로드가 이 코멘트를 받아서 한 일이 이 파이프라인의 성격을 잘 보여준다. 단순히 "start 파라미터를 붙였습니다"가 아니라 —

  1. 유튜브 임베드의 start=초 파라미터로 해결 가능함을 파악
  2. 16개 영상 각각의 watch 페이지를 실제로 받아와, "가장 많이 다시 본 구간"(Most replayed) 히트맵 데이터를 파싱해 하이라이트 시작점을 실측 (페이지 안의 macroMarkersListEntity JSON에서 intensityScoreNormalized가 최대인 구간)
  3. 예외 판단 — 두 곡은 0초 피크가 "다들 처음부터 트는" 왜곡이라 차순위 실제 코러스 피크를 채택, 한 곡은 히트맵 미제공이라 정직하게 처음부터 재생
  4. 수정 커밋 → 같은 브랜치에 푸시(프리뷰 자동 갱신) → PR에 "뭘 어떻게 고쳤는지" 답글 코멘트
팁: 수정 요청 코멘트는 "고쳐줘"보다 원하는 사용자 경험을 적는 게 좋다. "노래가 바로 나왔으면 좋겠다"라고 적으면 AI가 방법(하이라이트 실측)을 알아서 찾는다.

4. 3단계 — 스케줄링: 매일 새벽 5시에 클라우드에서

파이프라인이 검증됐으니 이제 사람이 하던 "제작" 부분을 스케줄된 클라우드 에이전트로 넘긴다. Claude Code에서 /schedule로 등록하면 되고, 핵심 스펙은 이렇다:

 

항목값메모

스케줄 0 20 * * * (UTC) = 매일 새벽 5시 KST. cron은 UTC 기준이라 -9시간 변환 필요. 최소 간격 1시간.
실행 위치 Anthropic 클라우드 내 컴퓨터가 꺼져 있어도 돌아간다. 저장소를 클론해서 작업.
모델 Opus 4.8 기본값은 Sonnet. 콘텐츠 문구 품질(사이트 특유의 톤)이 중요해서 상위 모델 선택.
도구 Bash, Read/Write/Edit, Glob/Grep, WebSearch/WebFetch 등 유행 조사에 웹검색이 필수라 꼭 포함.

여기서 삽질: GitHub 연결은 두 단계다

 

루틴 등록이 두 번 거절당했는데, 오류 코드가 힌트였다:

  • 401 "Connect your GitHub account" — claude.ai 계정과 GitHub 계정의 OAuth 연결이 안 된 상태. → claude.ai/code에서 GitHub 연결(Authorize).
  • 403 "You don't have access to a repository" — 계정 연결은 됐지만 Claude GitHub App이 해당 저장소에 설치되지 않은 상태. → github.com/apps/claude → Install → 저장소 선택.
주의: "Authorize"(계정 승인)와 "Install"(앱 설치)은 다른 단계다. GitHub 설정의 Authorized GitHub Apps에 Claude가 있어도, Installed GitHub Apps에 없으면 저장소 접근이 안 된다. 둘 다 끝나야 200이 떨어진다.

5. 실제 루틴 프롬프트 전문

스케줄 에이전트는 매일 빈 컨텍스트로 시작하므로, 프롬프트가 곧 시스템이다. 실제로 등록한 프롬프트 전문:

너는 놀랩(nolab.kr — 심심풀이 테스트·퀴즈·게임 사이트, Next.js App Router + Vercel 자동 배포)의
데일리 콘텐츠 제작 에이전트다. 오늘의 신규 콘텐츠 2~3개를 만들어 브랜치 하나 + PR 하나로 올려라.
main 직접 푸시는 절대 금지.

[0. 시작 전 필독]
- 저장소 루트 CLAUDE.md를 읽고 규칙을 따른다. 예외 하나: '개발 연구일지(옵시디언)' 기록은
  클라우드에서 접근 불가하므로 생략한다(주인이 로컬에서 몰아서 기록). 나머지 게이트(SEO,
  사이트 연구일지 changelog)는 전부 지킨다.
- docs/content-log.md(소재 대장)를 반드시 읽는다: '배포됨' 소재와 겹치는 컨셉 금지,
  '거절됨'·'금지' 소재는 다시 제안하지 않는다.
- lib/tests.ts(콘텐츠 카드 목록), 상식 글 데이터(SENSE_NOTES가 정의된 lib 파일),
  app/changelog/page.tsx 최근 엔트리, git log --oneline -20을 확인해 기존·최근 소재와
  겹치지 않게 한다.
- 놀랩 톤: 존댓말 + 하찮음 + 「연구소가 진지하게 측정하는 척」 세계관. 사용자 노출 문구 전부 이 톤.

[1. 유행 조사]
웹검색으로 한국 기준 요즘 유행(밈, 유행어, 화제, 시즌 소재)을 조사해 오늘 만들 소재를 고른다.
PR 본문에 「왜 이 소재인지」 근거(링크 포함)를 쓸 수 있어야 한다.

[2. 콘텐츠 2~3개 제작 — 유형을 섞을 것, 같은 유형만 3개 금지]
- 상식 글 1~2편: SENSE_NOTES 배열에 항목을 추가하는 기존 패턴 그대로. 텍스트만이라 가장 안전.
- 맞추기/퀴즈/테스트 1개: 기존 테스트·퀴즈 코드 패턴(lib/mental.ts + app/mental/ 등)을 참조해
  동일 구조로. 예: 유행어 뜻 맞추기, 세대 판별 테스트.
- 월드컵 변형: lib/song90.ts + app/song90/(유튜브 임베드 노래 월드컵) 또는 lib/chicken.ts 패턴
  재사용. 노래 월드컵 후속편(2000년대, 발라드 등)은 곡 데이터만 갈면 되므로 효율적.

[저작권 절대 규칙]
- 실존 인물(연예인 포함) 사진이나 웹에서 긁은 이미지·짤을 저장소에 넣는 것 금지.
- 이미지가 필요하면 직접 그린 SVG, 이모지, 유튜브 공식 임베드(iframe)만 사용.
- 유튜브 videoId는 기억으로 지어내지 말고 실제 검색으로 찾은 뒤, 전곡
  oEmbed(https://www.youtube.com/oembed?url=...) HTTP 200으로 임베드 허용을 검증한다.
  노래류는 watch 페이지의 Most replayed 히트맵(macroMarkersListEntity) 피크를 파싱해
  하이라이트 start 초도 넣는다(song90 방식).

[3. 품질 게이트 — 전부 통과해야 PR]
- SEO: lib/tests.ts에 seo(title/description) 채우고 app//layout.tsx 추가
  (app/mental/layout.tsx 복사).
- 새 콘텐츠 id를 lib/db.ts의 TESTS 화이트리스트에 추가.
- changelog: app/changelog/page.tsx ENTRIES 맨 위에 새 엔트리(date=오늘 KST,
  version=직전 버전에서 MINOR+1, 놀랩 톤 notes).
- npx tsc --noEmit && npm run build 통과 필수. 실패한 채 PR 금지.

[4. PR]
- 브랜치명: auto/daily-YYYYMMDD.
- 오늘 만든 소재를 docs/content-log.md '배포됨' 섹션 맨 위에 『YYYY-MM-DD id — 한 줄 설명』으로
  추가하는 변경도 같은 PR에 포함한다(머지 = 대장 기록).
- PR 본문에: 각 아이템 소개, 소재 선정 근거(유행 링크), 검증한 것(tsc/build/oEmbed 등),
  Vercel 프리뷰에서 확인할 것 체크리스트, 승인 방법 안내(Merge=배포, 코멘트=수정 요청,
  Close=폐기, 특정 아이템만 빼려면 코멘트로 요청).
- 확신 없는 부분(저작권 애매, 검증 못 한 것)은 PR에 솔직하게 표기한다. 완성도가 기준 미달인
  아이템은 억지로 채우지 말고 빼고, 뺐다고 PR에 적는다.

프롬프트 설계에서 중요했던 것들

  • "기존 패턴을 참조하라"를 파일 경로로 지목 — "우리 스타일로 만들어"가 아니라 lib/mental.ts, app/song90/처럼 정확한 참조 파일을 준다. 결과물의 일관성이 완전히 달라진다.
  • 검증을 명령어 수준으로 명시 — "검증해라"가 아니라 oEmbed curl 명령과 판정 기준(200/401/404)까지 적는다. AI의 "아마 되겠지"를 차단하는 방법은 실행 가능한 검증 절차뿐이다.
  • 금지 목록은 이유와 함께 — 저작권 규칙에 "왜"가 있으면 애매한 경계 사례에서도 올바른 쪽으로 판단한다.
  • "빼는 용기" 조항 — 완성도 미달이면 억지로 채우지 말고 빼고, 뺐다고 보고하라. 이 한 줄이 없으면 AI는 개수를 채우려고 어설픈 걸 밀어넣는다.
  • 솔직함 강제 — 검증 못 한 것, 애매한 것을 PR에 표기하게 한다. 검수하는 사람(나)의 시간을 아끼는 건 결국 이 정직성이다.
  • 환경 차이 명시 — 클라우드 에이전트는 내 로컬 노트(옵시디언)에 못 쓴다. 이런 "로컬 전용 규칙"은 예외를 명시하고 사람(또는 로컬 세션)이 나중에 백필한다.

6. 4단계 — 소재 대장: 중복과 재제안 막기

자동 제안이 매일 오면 두 가지 문제가 생긴다: ① 기존 콘텐츠와 비슷한 걸 또 만들거나 ② 내가 폐기(Close)한 소재를 며칠 뒤 다시 들고 온다. ①은 코드 읽기로 어느 정도 막히지만, ②는 기록이 없으면 못 막는다 — 닫힌 PR의 "왜 닫혔는지"는 어디에도 안 남기 때문이다.

해결책은 저장소에 소재 대장 파일(docs/content-log.md)을 두는 것:

# 놀랩 소재 대장

## 배포됨            ← 에이전트가 만든 소재를 같은 PR에서 여기 추가 (머지 = 기록)
- 2026-07-19 song90 — 90년대 노래 이상형 월드컵

## 거절됨 (다시 제안 금지)   ← PR을 Close할 때 주인이 한 줄 추가
- (아직 없음)

## 금지 소재          ← 처음부터 안 되는 것들
- 실존 인물 사진이 필요한 모든 컨셉 (초상권·저작권)
- 웹에서 긁은 이미지·짤이 필요한 컨셉
- 정치·종교·혐오 소재, 사행성 소재

루틴 프롬프트에 두 가지 의무를 넣었다: 제안 전에 대장을 읽을 것, 그리고 만든 소재를 같은 PR에서 대장에 추가할 것. 이러면 머지하는 순간 기록이 저절로 쌓인다. 유일한 수동 작업은 PR을 Close할 때 거절됨에 한 줄 적는 것뿐이다.

7. 수정·커밋·배포 규칙 정리

상황누가어떻게

신규 콘텐츠 제작 클라우드 에이전트 (매일 새벽 5시) 브랜치 auto/daily-YYYYMMDD + PR 1개. main 직접 푸시 금지.
배포 사람 PR에서 Merge 클릭 → Vercel이 main을 자동 배포. 배포 권한은 항상 사람에게.
수정 사람이 요청, AI가 수행 PR에 원하는 경험을 코멘트 → AI가 같은 브랜치에 수정 커밋 → 프리뷰 자동 갱신 + 답글 보고.
부분 채택 사람이 요청, AI가 수행 "두 번째 것만 빼줘" 코멘트 → 해당 아이템 제거 커밋.
폐기 사람 PR Close + 소재 대장 '거절됨'에 한 줄.
품질 검증 AI (PR 전 필수) 타입체크 + 프로덕션 빌드 + 데이터 실측 검증(oEmbed 등). 실패 상태로 PR 금지.

8. 안전장치 목록

  • main 보호 — 에이전트는 브랜치+PR만. 배포 버튼(Merge)은 사람 손에만 있다.
  • 저작권 3중 장치 — 실존 인물 사진·웹 이미지 저장 금지 / 이미지는 자작 SVG·이모지·유튜브 공식 임베드만 / 유튜브는 전곡 임베드 허용 검증.
  • 환각 차단 — 외부 데이터(video ID, 하이라이트 시점)는 전부 실측. "기억으로 지어내지 말 것"을 명시하고 검증 명령어를 준다.
  • 빌드 게이트 — 타입체크·빌드 실패 상태로는 PR을 못 열게 한다. 프리뷰가 안 뜨는 PR은 검수 시간 낭비다.
  • 소재 대장 — 중복 제안·거절 소재 재등장 방지.
  • 정직성 조항 — 검증 못 한 것·애매한 것은 PR에 표기, 미달 아이템은 빼고 보고.

9. 한계와 다음 계획

  • 코멘트 자동 반응은 아직 반자동 — 지금은 PR 코멘트를 로컬 클로드 세션에 보여주면 고쳐서 푸시한다. Claude GitHub Actions를 붙이면 코멘트에 @claude만 멘션해도 자동으로 수정이 돈다. 다음 단계.
  • 이미지 생성은 미해결 — 이미지가 꼭 필요한 콘텐츠(카드 일러스트 등)는 지금 자작 SVG로 때운다. 이미지 생성 API를 붙이면 완전 자동이 되지만 별도 과금이라 보류.
  • "비슷한 결" 중복은 판단의 영역 — 대장이 있어도 컨셉이 미묘하게 겹치는 제안은 나올 수 있다. 그래서 최종 승인은 여전히 사람 몫이고, 그게 이 설계의 의도이기도 하다.
  • 취향 학습 — 거절 사유를 대장에 쌓다 보면, 그 자체가 "주인 취향 데이터"가 된다. 나중에 프롬프트에 반영할 예정.

10. 정리 — 순서만 다시

  1. 사이트를 GitHub + Vercel(프리뷰 배포) 구조로 만든다.
  2. 저장소에 CLAUDE.md로 규칙(품질 게이트·톤·금지사항)을 박는다. 이 파일이 자동화의 뼈대다.
  3. 로컬 클로드와 함께 1호 콘텐츠를 브랜치 → PR → 프리뷰 → Merge로 수동 1회 돌려 파이프라인을 검증한다.
  4. PR 코멘트로 수정 요청 → 수정 커밋 → 프리뷰 갱신, 수정 루프도 1회 경험한다.
  5. claude.ai GitHub 계정 연결(OAuth) + Claude GitHub App 저장소 설치. (둘 다!)
  6. Claude Code에서 /schedule로 루틴 등록 — cron(UTC 변환 주의), 모델, 그리고 위의 프롬프트.
  7. 소재 대장 파일을 만들고 루틴에 읽기/쓰기 의무를 추가한다.
  8. 아침마다 폰으로 PR 열고: Merge / 코멘트 / Close.

 

 

결과적으로 내가 매일 하는 일은 "아침에 PR 하나 읽고 버튼 하나 누르기"로 줄었다. 콘텐츠 공장의 공장장이라기보다, 매일 아침 신제품 시식만 하는 사장에 가깝다. 이 구조는 콘텐츠 사이트가 아니어도 — 블로그, 문서, 데이터 갱신, 정기 리포트 등 "주기적으로 뭔가 만들어서 사람이 승인하면 반영되는" 모든 일에 그대로 이식할 수 있다.

 

p.s. 이런 밈은 나도 이런거 보면서 공부한다.

728x90
반응형

댓글