블로그 자동화 작업 브랜치를 기본 브랜치에 병합 - #25

Merged
Kevinlee7250 merged 1122 commits into
latestfrom
claude/blog-automation-system-KAbtE
Aug 24, 2026
Merged

블로그 자동화 작업 브랜치를 기본 브랜치에 병합#25
Kevinlee7250 merged 1122 commits into
latestfrom
claude/blog-automation-system-KAbtE

Conversation

@Kevinlee7250

Copy link
Copy Markdown
Owner

지금까지 워크플로 정의만 latest에 동기화하고(#22·#23·#24) 실행 코드는 작업 브랜치에 두었습니다. 러너가 작업 브랜치를 체크아웃해 쓰기 때문에 동작에는 문제가 없었지만, 두 브랜치가 1,122커밋 갈라진 상태입니다. 이번에 코드 전체를 기본 브랜치로 가져옵니다.

병합 전 확인한 것

로컬에서 실제로 머지를 돌려 결과를 확인했습니다.

Automatic merge went well
충돌 없음
추가 318 · 수정 67 · 삭제 1

kevin-blog-image-studio/ 보존됨. 이 디렉터리는 latest에만 있고 작업 브랜치에는 없어서 단순 diff로는 "28개 파일 삭제"로 보입니다. 공통 조상(2026-07-23)에도 없고 latest에서 나중에 추가된 것이라, 일반 머지에서는 그대로 남습니다 — 시뮬레이션으로 확인했습니다. 이 때문에 squash가 아니라 merge commit이 필요합니다.

삭제되는 파일 1개: .github/oauth-token-trigger — 보안상 제거된 OAuth 교환 워크플로의 잔재입니다. 공개 저장소에 인증 코드 조각이 남아 있던 것으로, 작업 브랜치가 4a39c349에서 이미 지웠습니다. 병합하면 기본 브랜치에서도 사라집니다.

주요 내용

이번 세션에서 고친 것들입니다.

조용한 실패 차단

  • CLAUDE_MODEL 시크릿이 모든 Claude 호출을 400으로 실패시키던 문제
  • 검토 대기 저장소 분리 (목록·본문·보관) + 워크플로 커밋 누락 검사
  • 대시보드 데이터 계약 검사 6종 — 죽은 연결·표시되지 않는 산출물·신선도 누락을 자동으로 잡습니다
  • 차트 라이브러리 로드 실패가 대시보드 전체를 죽이던 문제 (패널별 격리)

연결이 끊겨 있던 곳

  • 시리즈 회차 ↔ 글을 제목 대신 post_id로 연결. final_editor가 제목을 교정하는 순간 끊기던 구조였고, 41편 중 38편이 이미 미아였습니다
  • 저자 경험 자료가 비었는데 1인칭만 허용되던 구멍 차단

대시보드

  • 데이터 신선도 표시 (24개 파일 + 18개 패널 배지), 꺼진 기능은 '중단'이 아니라 '기능 꺼짐'으로 구분
  • 초기 전송 1,855KB → 1,101KB
  • 시스템 탭 26개 섹션을 보는 주기로 3층 정리
  • 기능 스위치를 화면에서 켜고 끄기

운영

  • 실행 이력 보존 12일 → 90일
  • AdSense 필수 페이지 확인 (읽기 전용)

검증

  • 테스트 695건 통과
  • 헤드리스 브라우저로 대시보드 주요 흐름 확인 (초기 로딩, 지연 로딩, 차트 실패, 스위치 토글, 신선도 판정)

병합 방식

merge commit으로 병합해야 합니다. squash하면 kevin-blog-image-studio/가 삭제됩니다.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69


Generated by Claude Code

github-actionsBotand others added 30 commits August 19, 2026 10:47
인수인계 문서 1번·3번 항목입니다.
1) index_priority가 gscProperty/site를 내보내지 않아, 대시보드 색인 패널의
`c.gscProperty || c.site`가 항상 빈 값이었습니다. 다음 색인 점검이 돌면
패널에서 어느 Search Console 속성을 열지 판단할 근거가 없어집니다.
gsc_indexing은 이미 검사에 성공한 속성을 gsc_property로 남기고 있으므로
(URL-프리픽스인지 sc-domain인지가 여기서만 확실해짐) 그 값을 그대로
넘기고, 없으면 site로 대체합니다.
현재 index_status.json에 이 키가 없는 것은 파일이 해당 변경 이전에
생성됐기 때문이며, 다음 점검부터 채워집니다.
2) docs/index (1).html 삭제 — 업로드 중 이름이 바뀌어 생긴 사본입니다.
docs/index.html과 바이트 단위로 동일한 것을 확인하고 지웠습니다.
테스트 3건 추가 (전체 396건 통과)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
인수인계 문서 7번 항목입니다.
docs/data/gsc.json의 sites에 blog2·blog3만 있고 blog1(www.hoguwhat.com)이
빠져 있었습니다. 커스텀 도메인은 Search Console에 도메인 속성(sc-domain:)으로
등록된 경우가 많아 URL-프리픽스 형식으로 조회하면 403이 나는데,
blog_analytics와 gsc_indexing에는 있는 재시도 폴백이 gsc_fetcher에만
없어서 403을 받고 그대로 포기했습니다.
색인 검사(gsc_indexing)에서는 blog1이 정상 조회되고 있어(7건),
문제가 이 모듈에 한정된다는 것도 확인했습니다.
- 403이면 sc-domain 형식으로 한 번 더 조회
- 두 형식 모두 실패할 때만 경고하고, 메시지에 속성 유형 확인을 안내
- 폴백 도입으로 죽은 코드가 된 URL 조립부 제거
- 테스트 7건 추가 (전체 403건 통과)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
6번(기존 발행글 주제 중복)은 조사 결과 실제 중복 콘텐츠가 아니었습니다.
blog1·blog2 발행글의 제목 완전 일치 0건, 키워드 중복 1건뿐이며 겹치는
것은 카테고리일 뿐입니다. 주제 이탈분 71건을 모두 비공개하면 blog2에
9건만 남아 콘텐츠 부족으로 오히려 AdSense 거절 위험이 커지므로,
사용자 판단으로 보류하고 색인 회복을 우선하기로 했습니다.
migrate_offtopic.py가 blog1 전용이고 주제 키워드가 옛 설정 기준으로
하드코딩돼 있다는 점도 함께 남겼습니다 — 그대로 실행하면 오분류합니다.
반증된 가설 표에 "주제 중복이 정책 문제" 항목을 추가했습니다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
워크플로 5개가 기본 브랜치에 반영돼 pending-cleanup(주 1회),
alert-watchdog(6시간마다) 스케줄이 실제로 동작하기 시작합니다.
공개 저장소에서 토큰을 교환하던 get-oauth-token.yml도 제거됐습니다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
실행 #40 결과를 읽다가 "제출 421 / 색인 0 = 0%"로 결론지을 뻔했습니다.
사이트맵 API의 contents[].indexed는 구글이 지원을 중단한 필드로 항상 0이며,
실제 색인 수가 아닙니다. 필드 이름이 indexedUrls면 다음 사람도 같은 오독을
하게 됩니다.
- indexedUrls → indexedUrlsDeprecated 로 이름 변경 (값 자체는 그대로 보존)
- sitemap 결과에 note 추가 — 데이터 옆에 경고가 함께 붙어 다니도록
- sitemap_state docstring에 근거와 대체 판단 방법(index_status.json) 명시
- 모듈 docstring 갱신: 첫 실행 결과 설정 문제가 아님이 확인됐고,
그럼에도 이 진단이 안전망으로 계속 필요한 이유를 남김
- HANDOFF에 제약 항목과 반증된 가설 한 줄 추가, 08-19 진단 결과 반영
(canWrite=true / blog1이 실제 sc-domain 속성 → gsc_fetcher 수정 검증됨)
- 테스트 2건 추가, 옛 이름을 쓰던 기존 테스트 1건 갱신 (전체 405건 통과)
대시보드는 이 숫자를 표시하지 않아 변경하지 않았습니다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
github-actionsBotand others added 26 commits August 23, 2026 15:44
adsense-policy-update 워크플로가 만든 파일을 계약 검사가 잡았습니다.
검증기·작성 프롬프트가 읽는 입력 파일이라 대시보드에 나올 이유가 없습니다.
이 파일이 생겼다는 것 자체가 빈 CLAUDE_MODEL 수정이 실제로 통했다는
증거이기도 합니다 — 그 전에는 이 워크플로의 Claude 호출이 400으로
전부 실패했습니다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
워크플로 커밋 목록에 post_details.json이 빠져 있던 동안 발행된 2편의
FAQ·출처가 버려졌습니다. 목록에는 "FAQ 3개" 배지가 남고 모달을 열면
비어 있는 상태였습니다.
원본은 logs/run_history.json에 남아 있었습니다 — 실행 이력은 별도 경로로
커밋되기 때문입니다. 그걸 대조해 되살렸습니다.
2026-08-23 FAQ 3 / 출처 5 9월 대형마트 할인정보 총정리…
2026-08-22 FAQ 3 / 출처 2 여수 밤바다 오션뷰 숙소…
검증: 전체 100건 중 배지와 내용이 어긋나는 글 0건
recover_post_details.py를 남깁니다. 일회성 스크립트로 때우면 다음에
같은 일이 생겼을 때 다시 처음부터 만들어야 합니다.
- 기본은 미리보기, --apply에서만 기록
- 제목과 키워드가 둘 다 맞아야 같은 글로 봅니다. 제목만 보면 재발행된
동명의 글과 섞입니다
- 원래 FAQ·출처가 없던 글은 대상이 아닙니다 (유실이 아니므로)
- 이력에도 없어 복구 불가한 건수를 따로 보고합니다
누락 자체는 test_workflow_pending_files.py가 막고, 저장소에 빈틈이
남아 있지 않은지는 새 테스트가 계속 확인합니다. 651건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
도구(inbound_linker)는 2026-08-21부터 정상 동작했습니다. 그런데 고아는
165건에서 172건으로 오히려 늘었습니다. 링크가 안 걸린 게 아니라 처리량이
모자랐습니다.
해소: 주 1회 × 10건 = 10건/주
생성: 발행 1건당 고아 1건 → 하루 1편이면 7건/주
순감: 3건/주 → 밀린 172건을 걷어내는 데 57주
내부 링크는 글을 쓸 때 "새 글 → 기존 글" 한 방향으로만 걸립니다. 그래서
모든 새 글은 아무도 가리키지 않는 고아로 태어납니다. 배치를 아무리 빨리
돌려도 발행이 계속되는 한 새 고아는 계속 생깁니다.
두 갈래로 고칩니다.
1. 발행 직후 그 글에 역방향 링크 (blog-run, 블로그 3개 각각)
진단(internal_link_audit)을 먼저 돌려 방금 올린 글을 고아 목록에 넣고,
inbound_linker --limit 5 --apply로 링크를 겁니다. 순서가 중요합니다 —
진단을 건너뛰면 새 글이 목록에 없어 그 글만 계속 고아로 남습니다.
발행을 막을 이유는 없으므로 실패해도 경고만 남깁니다.
산출물(internal_links·inbound_links)을 커밋 목록에 추가했습니다.
빠뜨리면 다음 실행이 낡은 고아 목록을 읽습니다.
2. 밀린 물량은 매일 배치로 (inbound-links, 주간 → 매일, 10 → 25건)
스케줄 실행에는 inputs가 없어 `inputs.limit || 'N'`의 N이 실제 값입니다.
입력란 default만 고치고 이쪽을 놓치면 조용히 옛 처리량으로 돌아가므로
둘을 맞추고 테스트로 고정했습니다.
→ 순감 25건/일, 밀린 172건은 약 7일. 이후 배치는 보조로 남습니다.
기대치에 대해: 같은 측정에서 색인 검사 20건 중 고아 9건과 링크 있는 글
11건이 똑같이 미색인이었습니다. 내부 링크를 만든다고 색인이 풀린다는 근거는
아직 없습니다 — 표본이 전부 미색인이라 인과를 판정할 수 없습니다. 사이트
구조상 정당한 개선으로 보고 진행하며, 색인 해결책으로 광고하지 않습니다.
이 사양이 되돌아가지 않도록 테스트 8건 추가. 659건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
전체 점검에서 author_profile.json이 100% 템플릿(`예: ISA 계좌`)이라
경험형 경로가 한 번도 켜진 적이 없다는 것을 확인했습니다. AdSense가 보는
E-E-A-T 중 Experience 축이 통째로 비어 있는 상태입니다.
① 안전 구멍부터 막았습니다
프롬프트는 경험이 매칭되면 "아래는 글쓴이의 실제 경험 자료입니다"라고
선언하고 1인칭을 허용합니다. 그런데 summary가 비어 있으면 그 선언 아래에
아무것도 못 넣은 채 허가만 나갑니다 — 가짜 체험담을 막으려던 장치가
거꾸로 뚫립니다. summary가 빈 항목은 매칭하지 않습니다.
② author_profile.json 구조를 실제 발행 키워드에 맞춰 정리
템플릿 예시를 걷어내고 4개 분야(금융·여행·골프·생활서비스)의 토픽과
대상 블로그를 채웠습니다. summary·detail은 비워 뒀습니다 — 실제 경험은
지어낼 수 없고, 비어 있는 동안은 매칭되지 않아 글에 영향이 없습니다.
③ author_profile_check.py — 조용한 실패를 눈에 보이게
이 파일은 비어 있어도 워크플로가 초록으로 끝나고 모든 글이 조사형으로
나갑니다. 그게 정상 동작이라 아무도 눈치채지 못합니다. 등록 현황과
"이 항목을 채우면 최근 글 몇 편이 경험형으로 바뀌는지"를 보여 줍니다.
판정은 _match_experience를 그대로 씁니다. 처음에 자체 부분문자열 매칭으로
셌다가 "골프"가 "골프장"과 맞는 것처럼 과대 보고했습니다(17편 → 실제 14편).
실제 매칭은 단어 단위이고, 규칙이 두 곳에 살면 반드시 어긋납니다.
현재 최근 40편 중 0편이 경험형이며, 4개 항목을 채우면 14편이 대상입니다.
민감정보 유입을 막는 검사(전화·주민번호·이메일 패턴)도 함께 넣었습니다.
테스트 12건 추가.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
회차와 글을 잇는 유일한 끈이 제목이었습니다. 그런데 final_editor가 생성 후
제목을 교정합니다(post_data["title"] = new_title). 그 순간 연결이 끊기고,
대시보드는 "매칭되는 포스트를 찾을 수 없습니다"만 띄웁니다 — 고장을 고친 게
아니라 고장 났을 때 띄울 메시지를 만들어 둔 상태였습니다.
점검 결과 pending_review 41편 중 38편이 검토 대기·보관·발행 목록·레지스트리
300건 어디에서도 제목으로 찾히지 않았습니다.
앞으로
- save_pending_posts가 배정한 id를 돌려줍니다. 건너뛴 항목도 빈 문자열로
자리를 지켜 호출부가 순서대로 짝지을 수 있습니다
- 시리즈 발행 흐름이 그 id를 회차에 기록합니다. 짝은 별도 목록으로 들고
다닙니다 — 회차에 임시 참조를 달면 루프 중간의 save_series가 그걸
series.json에 직렬화합니다
- 대시보드가 post_id를 먼저 보고, 없을 때만 제목·키워드로 폴백합니다
이미 끊긴 것
- series_relink.py: 제목 일치 → 앞부분 일치 → 그래도 없으면 status를
'lost'로. 'pending_review'로 두면 열 수 없는 일감이 검토 목록에 계속
남습니다. 왜 lost인지 사유도 함께 적어 나중에 다시 조사하지 않게 합니다
- 반영 결과: 앞부분 일치 3건 연결, 38건 lost
(done 15 / lost 38 / pending_review 3 / upload_failed 9)
탐색 범위를 넓히다 알게 된 것: posts.json은 최근 100편만 담고 있어 7월 회차를
못 봅니다. 레지스트리 300건까지 넣어 다시 확인한 뒤에야 "어디에도 없다"고
결론 냈습니다. 처음 범위로는 성급한 판정이 될 뻔했습니다.
제목 앞부분 매칭은 앞 14자 안에서 갈라지면 실패합니다. 그 한계를 테스트로
고정해 두었고, 그래서 앞으로는 id로 잇습니다.
테스트 9건 추가, 680건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
page_creator.py가 개인정보처리방침·소개 페이지를 만들 수 있는데, 그걸 부르는
진입점은 main.py --setup 하나뿐이고 어떤 워크플로도 그 경로를 실행하지
않습니다. 누군가 로컬에서 한 번 돌렸는지에 달려 있고, 돌렸더라도 나중에
지워졌는지 아무도 모릅니다. 심사에서 바로 걸리는 항목인데 시스템 어디에도
"있다/없다"가 기록되지 않았습니다.
- required_pages_check.py: 블로그별로 세 페이지(개인정보처리방침·소개·문의)
존재 여부만 확인합니다. 만들지 않습니다 — 발행 중인 블로그에 페이지를
얹는 건 사람이 정할 일이라, 없으면 만드는 명령만 알립니다
- 제목 별칭 매칭: 손으로 만들었다면 "개인정보 처리방침"처럼 띄어쓰기가
다를 수 있어 별칭으로도 찾습니다
- adsense-audit에 읽기 전용 단계 추가 + 리포트 커밋
- 대시보드 AdSense 감사 패널 위에 상태 한 줄
가장 신경 쓴 부분은 "모른다"와 "없다"를 섞지 않는 것입니다. 토큰 발급에
실패하면 확인 불가로 기록하고 누락으로 세지 않습니다 — 그러지 않으면
멀쩡한 블로그를 고치라고 하게 됩니다. 반대로 한 블로그도 확인하지 못했으면
ok로 끝내지 않습니다 (사이트맵에서 "한 건도 못 보냈는데 실패 0이라 성공"으로
읽던 것과 같은 유형).
이 환경에서는 자격증명이 없어 실제 페이지 존재 여부를 확인하지 못했습니다.
현재 리포트는 unknown 3 / ok false이며, 워크플로 첫 실행에서 실제 상태가
나옵니다.
헤드리스로 세 가지 표시(확인 불가·누락·정상)를 모두 확인했습니다.
테스트 8건 추가, 688건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
지금까지 스위치는 화면에 상태만 보여 줬고, 바꾸려면 저장소 파일을 직접
고쳐야 했습니다. 이제 버튼으로 바꿉니다.
동작
- GitHub API로 blog-automation/feature_flags.json에 커밋합니다
(쿠팡 링크 저장과 같은 방식 — 대시보드는 정적 페이지라 파일을 직접
고치지 못합니다). 러너가 읽는 그 파일이 원본이고, docs/data 쪽은 표시용
사본입니다
- 저장 전에 최신 내용을 받아 그 위에 한 항목만 바꿉니다. 화면의 사본으로
통째로 덮어쓰면 그 사이 다른 곳에서 바뀐 스위치를 되돌립니다
- PAT가 없으면 버튼을 비활성화하고 어디서 설정하는지 안내합니다
- 표에 없는 새 스위치도 이름 그대로 화면에 나옵니다
social_content 스위치 신설 + 배선
SOCIAL_AUTO_PUBLISH=false여도 콘텐츠는 매번 만들고 게시만 건너뛰었습니다.
실제로 50건 중 39건이 전부 skipped이고 게시는 0건인데, 생성에 드는 Claude
호출과 356KB는 계속 나갔습니다. "게시하지 않는다"와 "만들지도 않는다"는
다른 결정이라 스위치를 따로 뒀고, 확인을 생성 앞에 뒀습니다 — 만들고 나서
버리면 비용은 이미 나간 뒤입니다.
기본값은 꺼짐입니다. 켤 생각이 없는 기능을 계속 돌리고 있었으므로,
대시보드에서 언제든 다시 켤 수 있게 된 지금이 끄기 적절한 시점입니다.
헤드리스 검증
PAT 없음: 버튼 2개 전부 비활성 + 안내 문구
PAT 있음: GET → PUT 순서로 호출, 커밋될 내용에 화면에 없던
other_flag:true가 그대로 보존됨 (덮어쓰기 아님), UI 즉시 갱신
테스트 5건 추가 (스위치를 아무도 안 읽는 장식으로 두지 않도록 main.py가
실제로 확인하는지, 확인이 생성보다 먼저인지, 화면 이름이 빠진 스위치가
없는지 포함). 693건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
소셜 콘텐츠 생성을 끈 직후에 생기는 문제 두 가지를 함께 처리합니다.
기능을 끄는 것만으로는 끝이 아니라, 끈 뒤의 화면도 정직해야 합니다.
① 신선도 패널이 꺼진 기능을 '중단'으로 오인
social.json은 이제 안 늙는 게 정상인데, 기대 주기(24시간)를 넘기면
빨간 '⚠️ 중단'으로 떴을 겁니다. 일부러 끈 기능과 진짜 고장이 같은
색으로 보이면 경고가 의미를 잃습니다.
- SOURCE_META 항목에 그 파일을 만드는 기능 스위치를 적을 수 있게
했습니다: ['소셜 콘텐츠', 24, 'social_content']
- 스위치가 꺼져 있으면 '⏸ 기능 꺼짐'(회색)으로 표시하고 지연 집계에서
제외합니다. 요약에는 "기능 꺼짐 N개"로 따로 셉니다
② 소셜 탭이 옛 기록을 현재처럼 보여 줌
목록에 50건이 그대로 남아 있어, 안내가 없으면 여전히 돌아가는 줄
압니다. 생성이 꺼져 있을 때만 뜨는 안내를 넣었습니다 — 몇 건이
언제까지의 기록인지, 다시 켜려면 어디로 가는지까지.
스위치 이름 오타로 연결이 조용히 끊기면 ①이 원래대로 돌아가므로,
SOURCE_META가 가리키는 스위치가 실제로 있는지 검사합니다. 오타를 넣어
검사가 잡는 것도 확인했습니다.
헤드리스 확인
꺼짐: '⏸ 기능 꺼짐' + "1개 넘김 · 기능 꺼짐 1개" + 소셜 탭 안내 표시
켜짐: 같은 파일이 '⚠️ 30일 전'으로 지연 집계에 포함, 안내 숨김
테스트 2건 추가, 695건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
@Kevinlee7250
Kevinlee7250 marked this pull request as ready for review August 24, 2026 04:26
@Kevinlee7250
Kevinlee7250 merged commit 67cab1a into latestAug 24, 2026
3 checks passed
github-actionsBot added a commit that referenced this pull request Aug 25, 2026
github-actionsBot added a commit that referenced this pull request Aug 26, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@Kevinlee7250@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

블로그 자동화 작업 브랜치를 기본 브랜치에 병합 - #25

Merged
Kevinlee7250 merged 1122 commits into
latestfrom
claude/blog-automation-system-KAbtE
Aug 24, 2026
Merged

블로그 자동화 작업 브랜치를 기본 브랜치에 병합#25
Kevinlee7250 merged 1122 commits into
latestfrom
claude/blog-automation-system-KAbtE

Conversation

@Kevinlee7250

Copy link
Copy Markdown
Owner

지금까지 워크플로 정의만 latest에 동기화하고(#22·#23·#24) 실행 코드는 작업 브랜치에 두었습니다. 러너가 작업 브랜치를 체크아웃해 쓰기 때문에 동작에는 문제가 없었지만, 두 브랜치가 1,122커밋 갈라진 상태입니다. 이번에 코드 전체를 기본 브랜치로 가져옵니다.

병합 전 확인한 것

로컬에서 실제로 머지를 돌려 결과를 확인했습니다.

Automatic merge went well
충돌 없음
추가 318 · 수정 67 · 삭제 1

kevin-blog-image-studio/ 보존됨. 이 디렉터리는 latest에만 있고 작업 브랜치에는 없어서 단순 diff로는 "28개 파일 삭제"로 보입니다. 공통 조상(2026-07-23)에도 없고 latest에서 나중에 추가된 것이라, 일반 머지에서는 그대로 남습니다 — 시뮬레이션으로 확인했습니다. 이 때문에 squash가 아니라 merge commit이 필요합니다.

삭제되는 파일 1개: .github/oauth-token-trigger — 보안상 제거된 OAuth 교환 워크플로의 잔재입니다. 공개 저장소에 인증 코드 조각이 남아 있던 것으로, 작업 브랜치가 4a39c349에서 이미 지웠습니다. 병합하면 기본 브랜치에서도 사라집니다.

주요 내용

이번 세션에서 고친 것들입니다.

조용한 실패 차단

  • CLAUDE_MODEL 시크릿이 모든 Claude 호출을 400으로 실패시키던 문제
  • 검토 대기 저장소 분리 (목록·본문·보관) + 워크플로 커밋 누락 검사
  • 대시보드 데이터 계약 검사 6종 — 죽은 연결·표시되지 않는 산출물·신선도 누락을 자동으로 잡습니다
  • 차트 라이브러리 로드 실패가 대시보드 전체를 죽이던 문제 (패널별 격리)

연결이 끊겨 있던 곳

  • 시리즈 회차 ↔ 글을 제목 대신 post_id로 연결. final_editor가 제목을 교정하는 순간 끊기던 구조였고, 41편 중 38편이 이미 미아였습니다
  • 저자 경험 자료가 비었는데 1인칭만 허용되던 구멍 차단

대시보드

  • 데이터 신선도 표시 (24개 파일 + 18개 패널 배지), 꺼진 기능은 '중단'이 아니라 '기능 꺼짐'으로 구분
  • 초기 전송 1,855KB → 1,101KB
  • 시스템 탭 26개 섹션을 보는 주기로 3층 정리
  • 기능 스위치를 화면에서 켜고 끄기

운영

  • 실행 이력 보존 12일 → 90일
  • AdSense 필수 페이지 확인 (읽기 전용)

검증

  • 테스트 695건 통과
  • 헤드리스 브라우저로 대시보드 주요 흐름 확인 (초기 로딩, 지연 로딩, 차트 실패, 스위치 토글, 신선도 판정)

병합 방식

merge commit으로 병합해야 합니다. squash하면 kevin-blog-image-studio/가 삭제됩니다.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69


Generated by Claude Code

github-actionsBotand others added 30 commits August 19, 2026 10:47
인수인계 문서 1번·3번 항목입니다.
1) index_priority가 gscProperty/site를 내보내지 않아, 대시보드 색인 패널의
`c.gscProperty || c.site`가 항상 빈 값이었습니다. 다음 색인 점검이 돌면
패널에서 어느 Search Console 속성을 열지 판단할 근거가 없어집니다.
gsc_indexing은 이미 검사에 성공한 속성을 gsc_property로 남기고 있으므로
(URL-프리픽스인지 sc-domain인지가 여기서만 확실해짐) 그 값을 그대로
넘기고, 없으면 site로 대체합니다.
현재 index_status.json에 이 키가 없는 것은 파일이 해당 변경 이전에
생성됐기 때문이며, 다음 점검부터 채워집니다.
2) docs/index (1).html 삭제 — 업로드 중 이름이 바뀌어 생긴 사본입니다.
docs/index.html과 바이트 단위로 동일한 것을 확인하고 지웠습니다.
테스트 3건 추가 (전체 396건 통과)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
인수인계 문서 7번 항목입니다.
docs/data/gsc.json의 sites에 blog2·blog3만 있고 blog1(www.hoguwhat.com)이
빠져 있었습니다. 커스텀 도메인은 Search Console에 도메인 속성(sc-domain:)으로
등록된 경우가 많아 URL-프리픽스 형식으로 조회하면 403이 나는데,
blog_analytics와 gsc_indexing에는 있는 재시도 폴백이 gsc_fetcher에만
없어서 403을 받고 그대로 포기했습니다.
색인 검사(gsc_indexing)에서는 blog1이 정상 조회되고 있어(7건),
문제가 이 모듈에 한정된다는 것도 확인했습니다.
- 403이면 sc-domain 형식으로 한 번 더 조회
- 두 형식 모두 실패할 때만 경고하고, 메시지에 속성 유형 확인을 안내
- 폴백 도입으로 죽은 코드가 된 URL 조립부 제거
- 테스트 7건 추가 (전체 403건 통과)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
6번(기존 발행글 주제 중복)은 조사 결과 실제 중복 콘텐츠가 아니었습니다.
blog1·blog2 발행글의 제목 완전 일치 0건, 키워드 중복 1건뿐이며 겹치는
것은 카테고리일 뿐입니다. 주제 이탈분 71건을 모두 비공개하면 blog2에
9건만 남아 콘텐츠 부족으로 오히려 AdSense 거절 위험이 커지므로,
사용자 판단으로 보류하고 색인 회복을 우선하기로 했습니다.
migrate_offtopic.py가 blog1 전용이고 주제 키워드가 옛 설정 기준으로
하드코딩돼 있다는 점도 함께 남겼습니다 — 그대로 실행하면 오분류합니다.
반증된 가설 표에 "주제 중복이 정책 문제" 항목을 추가했습니다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
워크플로 5개가 기본 브랜치에 반영돼 pending-cleanup(주 1회),
alert-watchdog(6시간마다) 스케줄이 실제로 동작하기 시작합니다.
공개 저장소에서 토큰을 교환하던 get-oauth-token.yml도 제거됐습니다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
실행 #40 결과를 읽다가 "제출 421 / 색인 0 = 0%"로 결론지을 뻔했습니다.
사이트맵 API의 contents[].indexed는 구글이 지원을 중단한 필드로 항상 0이며,
실제 색인 수가 아닙니다. 필드 이름이 indexedUrls면 다음 사람도 같은 오독을
하게 됩니다.
- indexedUrls → indexedUrlsDeprecated 로 이름 변경 (값 자체는 그대로 보존)
- sitemap 결과에 note 추가 — 데이터 옆에 경고가 함께 붙어 다니도록
- sitemap_state docstring에 근거와 대체 판단 방법(index_status.json) 명시
- 모듈 docstring 갱신: 첫 실행 결과 설정 문제가 아님이 확인됐고,
그럼에도 이 진단이 안전망으로 계속 필요한 이유를 남김
- HANDOFF에 제약 항목과 반증된 가설 한 줄 추가, 08-19 진단 결과 반영
(canWrite=true / blog1이 실제 sc-domain 속성 → gsc_fetcher 수정 검증됨)
- 테스트 2건 추가, 옛 이름을 쓰던 기존 테스트 1건 갱신 (전체 405건 통과)
대시보드는 이 숫자를 표시하지 않아 변경하지 않았습니다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
github-actionsBotand others added 26 commits August 23, 2026 15:44
adsense-policy-update 워크플로가 만든 파일을 계약 검사가 잡았습니다.
검증기·작성 프롬프트가 읽는 입력 파일이라 대시보드에 나올 이유가 없습니다.
이 파일이 생겼다는 것 자체가 빈 CLAUDE_MODEL 수정이 실제로 통했다는
증거이기도 합니다 — 그 전에는 이 워크플로의 Claude 호출이 400으로
전부 실패했습니다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
워크플로 커밋 목록에 post_details.json이 빠져 있던 동안 발행된 2편의
FAQ·출처가 버려졌습니다. 목록에는 "FAQ 3개" 배지가 남고 모달을 열면
비어 있는 상태였습니다.
원본은 logs/run_history.json에 남아 있었습니다 — 실행 이력은 별도 경로로
커밋되기 때문입니다. 그걸 대조해 되살렸습니다.
2026-08-23 FAQ 3 / 출처 5 9월 대형마트 할인정보 총정리…
2026-08-22 FAQ 3 / 출처 2 여수 밤바다 오션뷰 숙소…
검증: 전체 100건 중 배지와 내용이 어긋나는 글 0건
recover_post_details.py를 남깁니다. 일회성 스크립트로 때우면 다음에
같은 일이 생겼을 때 다시 처음부터 만들어야 합니다.
- 기본은 미리보기, --apply에서만 기록
- 제목과 키워드가 둘 다 맞아야 같은 글로 봅니다. 제목만 보면 재발행된
동명의 글과 섞입니다
- 원래 FAQ·출처가 없던 글은 대상이 아닙니다 (유실이 아니므로)
- 이력에도 없어 복구 불가한 건수를 따로 보고합니다
누락 자체는 test_workflow_pending_files.py가 막고, 저장소에 빈틈이
남아 있지 않은지는 새 테스트가 계속 확인합니다. 651건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
도구(inbound_linker)는 2026-08-21부터 정상 동작했습니다. 그런데 고아는
165건에서 172건으로 오히려 늘었습니다. 링크가 안 걸린 게 아니라 처리량이
모자랐습니다.
해소: 주 1회 × 10건 = 10건/주
생성: 발행 1건당 고아 1건 → 하루 1편이면 7건/주
순감: 3건/주 → 밀린 172건을 걷어내는 데 57주
내부 링크는 글을 쓸 때 "새 글 → 기존 글" 한 방향으로만 걸립니다. 그래서
모든 새 글은 아무도 가리키지 않는 고아로 태어납니다. 배치를 아무리 빨리
돌려도 발행이 계속되는 한 새 고아는 계속 생깁니다.
두 갈래로 고칩니다.
1. 발행 직후 그 글에 역방향 링크 (blog-run, 블로그 3개 각각)
진단(internal_link_audit)을 먼저 돌려 방금 올린 글을 고아 목록에 넣고,
inbound_linker --limit 5 --apply로 링크를 겁니다. 순서가 중요합니다 —
진단을 건너뛰면 새 글이 목록에 없어 그 글만 계속 고아로 남습니다.
발행을 막을 이유는 없으므로 실패해도 경고만 남깁니다.
산출물(internal_links·inbound_links)을 커밋 목록에 추가했습니다.
빠뜨리면 다음 실행이 낡은 고아 목록을 읽습니다.
2. 밀린 물량은 매일 배치로 (inbound-links, 주간 → 매일, 10 → 25건)
스케줄 실행에는 inputs가 없어 `inputs.limit || 'N'`의 N이 실제 값입니다.
입력란 default만 고치고 이쪽을 놓치면 조용히 옛 처리량으로 돌아가므로
둘을 맞추고 테스트로 고정했습니다.
→ 순감 25건/일, 밀린 172건은 약 7일. 이후 배치는 보조로 남습니다.
기대치에 대해: 같은 측정에서 색인 검사 20건 중 고아 9건과 링크 있는 글
11건이 똑같이 미색인이었습니다. 내부 링크를 만든다고 색인이 풀린다는 근거는
아직 없습니다 — 표본이 전부 미색인이라 인과를 판정할 수 없습니다. 사이트
구조상 정당한 개선으로 보고 진행하며, 색인 해결책으로 광고하지 않습니다.
이 사양이 되돌아가지 않도록 테스트 8건 추가. 659건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
전체 점검에서 author_profile.json이 100% 템플릿(`예: ISA 계좌`)이라
경험형 경로가 한 번도 켜진 적이 없다는 것을 확인했습니다. AdSense가 보는
E-E-A-T 중 Experience 축이 통째로 비어 있는 상태입니다.
① 안전 구멍부터 막았습니다
프롬프트는 경험이 매칭되면 "아래는 글쓴이의 실제 경험 자료입니다"라고
선언하고 1인칭을 허용합니다. 그런데 summary가 비어 있으면 그 선언 아래에
아무것도 못 넣은 채 허가만 나갑니다 — 가짜 체험담을 막으려던 장치가
거꾸로 뚫립니다. summary가 빈 항목은 매칭하지 않습니다.
② author_profile.json 구조를 실제 발행 키워드에 맞춰 정리
템플릿 예시를 걷어내고 4개 분야(금융·여행·골프·생활서비스)의 토픽과
대상 블로그를 채웠습니다. summary·detail은 비워 뒀습니다 — 실제 경험은
지어낼 수 없고, 비어 있는 동안은 매칭되지 않아 글에 영향이 없습니다.
③ author_profile_check.py — 조용한 실패를 눈에 보이게
이 파일은 비어 있어도 워크플로가 초록으로 끝나고 모든 글이 조사형으로
나갑니다. 그게 정상 동작이라 아무도 눈치채지 못합니다. 등록 현황과
"이 항목을 채우면 최근 글 몇 편이 경험형으로 바뀌는지"를 보여 줍니다.
판정은 _match_experience를 그대로 씁니다. 처음에 자체 부분문자열 매칭으로
셌다가 "골프"가 "골프장"과 맞는 것처럼 과대 보고했습니다(17편 → 실제 14편).
실제 매칭은 단어 단위이고, 규칙이 두 곳에 살면 반드시 어긋납니다.
현재 최근 40편 중 0편이 경험형이며, 4개 항목을 채우면 14편이 대상입니다.
민감정보 유입을 막는 검사(전화·주민번호·이메일 패턴)도 함께 넣었습니다.
테스트 12건 추가.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
회차와 글을 잇는 유일한 끈이 제목이었습니다. 그런데 final_editor가 생성 후
제목을 교정합니다(post_data["title"] = new_title). 그 순간 연결이 끊기고,
대시보드는 "매칭되는 포스트를 찾을 수 없습니다"만 띄웁니다 — 고장을 고친 게
아니라 고장 났을 때 띄울 메시지를 만들어 둔 상태였습니다.
점검 결과 pending_review 41편 중 38편이 검토 대기·보관·발행 목록·레지스트리
300건 어디에서도 제목으로 찾히지 않았습니다.
앞으로
- save_pending_posts가 배정한 id를 돌려줍니다. 건너뛴 항목도 빈 문자열로
자리를 지켜 호출부가 순서대로 짝지을 수 있습니다
- 시리즈 발행 흐름이 그 id를 회차에 기록합니다. 짝은 별도 목록으로 들고
다닙니다 — 회차에 임시 참조를 달면 루프 중간의 save_series가 그걸
series.json에 직렬화합니다
- 대시보드가 post_id를 먼저 보고, 없을 때만 제목·키워드로 폴백합니다
이미 끊긴 것
- series_relink.py: 제목 일치 → 앞부분 일치 → 그래도 없으면 status를
'lost'로. 'pending_review'로 두면 열 수 없는 일감이 검토 목록에 계속
남습니다. 왜 lost인지 사유도 함께 적어 나중에 다시 조사하지 않게 합니다
- 반영 결과: 앞부분 일치 3건 연결, 38건 lost
(done 15 / lost 38 / pending_review 3 / upload_failed 9)
탐색 범위를 넓히다 알게 된 것: posts.json은 최근 100편만 담고 있어 7월 회차를
못 봅니다. 레지스트리 300건까지 넣어 다시 확인한 뒤에야 "어디에도 없다"고
결론 냈습니다. 처음 범위로는 성급한 판정이 될 뻔했습니다.
제목 앞부분 매칭은 앞 14자 안에서 갈라지면 실패합니다. 그 한계를 테스트로
고정해 두었고, 그래서 앞으로는 id로 잇습니다.
테스트 9건 추가, 680건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
page_creator.py가 개인정보처리방침·소개 페이지를 만들 수 있는데, 그걸 부르는
진입점은 main.py --setup 하나뿐이고 어떤 워크플로도 그 경로를 실행하지
않습니다. 누군가 로컬에서 한 번 돌렸는지에 달려 있고, 돌렸더라도 나중에
지워졌는지 아무도 모릅니다. 심사에서 바로 걸리는 항목인데 시스템 어디에도
"있다/없다"가 기록되지 않았습니다.
- required_pages_check.py: 블로그별로 세 페이지(개인정보처리방침·소개·문의)
존재 여부만 확인합니다. 만들지 않습니다 — 발행 중인 블로그에 페이지를
얹는 건 사람이 정할 일이라, 없으면 만드는 명령만 알립니다
- 제목 별칭 매칭: 손으로 만들었다면 "개인정보 처리방침"처럼 띄어쓰기가
다를 수 있어 별칭으로도 찾습니다
- adsense-audit에 읽기 전용 단계 추가 + 리포트 커밋
- 대시보드 AdSense 감사 패널 위에 상태 한 줄
가장 신경 쓴 부분은 "모른다"와 "없다"를 섞지 않는 것입니다. 토큰 발급에
실패하면 확인 불가로 기록하고 누락으로 세지 않습니다 — 그러지 않으면
멀쩡한 블로그를 고치라고 하게 됩니다. 반대로 한 블로그도 확인하지 못했으면
ok로 끝내지 않습니다 (사이트맵에서 "한 건도 못 보냈는데 실패 0이라 성공"으로
읽던 것과 같은 유형).
이 환경에서는 자격증명이 없어 실제 페이지 존재 여부를 확인하지 못했습니다.
현재 리포트는 unknown 3 / ok false이며, 워크플로 첫 실행에서 실제 상태가
나옵니다.
헤드리스로 세 가지 표시(확인 불가·누락·정상)를 모두 확인했습니다.
테스트 8건 추가, 688건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
지금까지 스위치는 화면에 상태만 보여 줬고, 바꾸려면 저장소 파일을 직접
고쳐야 했습니다. 이제 버튼으로 바꿉니다.
동작
- GitHub API로 blog-automation/feature_flags.json에 커밋합니다
(쿠팡 링크 저장과 같은 방식 — 대시보드는 정적 페이지라 파일을 직접
고치지 못합니다). 러너가 읽는 그 파일이 원본이고, docs/data 쪽은 표시용
사본입니다
- 저장 전에 최신 내용을 받아 그 위에 한 항목만 바꿉니다. 화면의 사본으로
통째로 덮어쓰면 그 사이 다른 곳에서 바뀐 스위치를 되돌립니다
- PAT가 없으면 버튼을 비활성화하고 어디서 설정하는지 안내합니다
- 표에 없는 새 스위치도 이름 그대로 화면에 나옵니다
social_content 스위치 신설 + 배선
SOCIAL_AUTO_PUBLISH=false여도 콘텐츠는 매번 만들고 게시만 건너뛰었습니다.
실제로 50건 중 39건이 전부 skipped이고 게시는 0건인데, 생성에 드는 Claude
호출과 356KB는 계속 나갔습니다. "게시하지 않는다"와 "만들지도 않는다"는
다른 결정이라 스위치를 따로 뒀고, 확인을 생성 앞에 뒀습니다 — 만들고 나서
버리면 비용은 이미 나간 뒤입니다.
기본값은 꺼짐입니다. 켤 생각이 없는 기능을 계속 돌리고 있었으므로,
대시보드에서 언제든 다시 켤 수 있게 된 지금이 끄기 적절한 시점입니다.
헤드리스 검증
PAT 없음: 버튼 2개 전부 비활성 + 안내 문구
PAT 있음: GET → PUT 순서로 호출, 커밋될 내용에 화면에 없던
other_flag:true가 그대로 보존됨 (덮어쓰기 아님), UI 즉시 갱신
테스트 5건 추가 (스위치를 아무도 안 읽는 장식으로 두지 않도록 main.py가
실제로 확인하는지, 확인이 생성보다 먼저인지, 화면 이름이 빠진 스위치가
없는지 포함). 693건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
소셜 콘텐츠 생성을 끈 직후에 생기는 문제 두 가지를 함께 처리합니다.
기능을 끄는 것만으로는 끝이 아니라, 끈 뒤의 화면도 정직해야 합니다.
① 신선도 패널이 꺼진 기능을 '중단'으로 오인
social.json은 이제 안 늙는 게 정상인데, 기대 주기(24시간)를 넘기면
빨간 '⚠️ 중단'으로 떴을 겁니다. 일부러 끈 기능과 진짜 고장이 같은
색으로 보이면 경고가 의미를 잃습니다.
- SOURCE_META 항목에 그 파일을 만드는 기능 스위치를 적을 수 있게
했습니다: ['소셜 콘텐츠', 24, 'social_content']
- 스위치가 꺼져 있으면 '⏸ 기능 꺼짐'(회색)으로 표시하고 지연 집계에서
제외합니다. 요약에는 "기능 꺼짐 N개"로 따로 셉니다
② 소셜 탭이 옛 기록을 현재처럼 보여 줌
목록에 50건이 그대로 남아 있어, 안내가 없으면 여전히 돌아가는 줄
압니다. 생성이 꺼져 있을 때만 뜨는 안내를 넣었습니다 — 몇 건이
언제까지의 기록인지, 다시 켜려면 어디로 가는지까지.
스위치 이름 오타로 연결이 조용히 끊기면 ①이 원래대로 돌아가므로,
SOURCE_META가 가리키는 스위치가 실제로 있는지 검사합니다. 오타를 넣어
검사가 잡는 것도 확인했습니다.
헤드리스 확인
꺼짐: '⏸ 기능 꺼짐' + "1개 넘김 · 기능 꺼짐 1개" + 소셜 탭 안내 표시
켜짐: 같은 파일이 '⚠️ 30일 전'으로 지연 집계에 포함, 안내 숨김
테스트 2건 추가, 695건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
@Kevinlee7250
Kevinlee7250 marked this pull request as ready for review August 24, 2026 04:26
@Kevinlee7250
Kevinlee7250 merged commit 67cab1a into latestAug 24, 2026
3 checks passed
github-actionsBot added a commit that referenced this pull request Aug 25, 2026
github-actionsBot added a commit that referenced this pull request Aug 26, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@Kevinlee7250@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

블로그 자동화 작업 브랜치를 기본 브랜치에 병합 - #25

Merged
Kevinlee7250 merged 1122 commits into
latestfrom
claude/blog-automation-system-KAbtE
Aug 24, 2026
Merged

블로그 자동화 작업 브랜치를 기본 브랜치에 병합#25
Kevinlee7250 merged 1122 commits into
latestfrom
claude/blog-automation-system-KAbtE

Conversation

@Kevinlee7250

Copy link
Copy Markdown
Owner

지금까지 워크플로 정의만 latest에 동기화하고(#22·#23·#24) 실행 코드는 작업 브랜치에 두었습니다. 러너가 작업 브랜치를 체크아웃해 쓰기 때문에 동작에는 문제가 없었지만, 두 브랜치가 1,122커밋 갈라진 상태입니다. 이번에 코드 전체를 기본 브랜치로 가져옵니다.

병합 전 확인한 것

로컬에서 실제로 머지를 돌려 결과를 확인했습니다.

Automatic merge went well
충돌 없음
추가 318 · 수정 67 · 삭제 1

kevin-blog-image-studio/ 보존됨. 이 디렉터리는 latest에만 있고 작업 브랜치에는 없어서 단순 diff로는 "28개 파일 삭제"로 보입니다. 공통 조상(2026-07-23)에도 없고 latest에서 나중에 추가된 것이라, 일반 머지에서는 그대로 남습니다 — 시뮬레이션으로 확인했습니다. 이 때문에 squash가 아니라 merge commit이 필요합니다.

삭제되는 파일 1개: .github/oauth-token-trigger — 보안상 제거된 OAuth 교환 워크플로의 잔재입니다. 공개 저장소에 인증 코드 조각이 남아 있던 것으로, 작업 브랜치가 4a39c349에서 이미 지웠습니다. 병합하면 기본 브랜치에서도 사라집니다.

주요 내용

이번 세션에서 고친 것들입니다.

조용한 실패 차단

  • CLAUDE_MODEL 시크릿이 모든 Claude 호출을 400으로 실패시키던 문제
  • 검토 대기 저장소 분리 (목록·본문·보관) + 워크플로 커밋 누락 검사
  • 대시보드 데이터 계약 검사 6종 — 죽은 연결·표시되지 않는 산출물·신선도 누락을 자동으로 잡습니다
  • 차트 라이브러리 로드 실패가 대시보드 전체를 죽이던 문제 (패널별 격리)

연결이 끊겨 있던 곳

  • 시리즈 회차 ↔ 글을 제목 대신 post_id로 연결. final_editor가 제목을 교정하는 순간 끊기던 구조였고, 41편 중 38편이 이미 미아였습니다
  • 저자 경험 자료가 비었는데 1인칭만 허용되던 구멍 차단

대시보드

  • 데이터 신선도 표시 (24개 파일 + 18개 패널 배지), 꺼진 기능은 '중단'이 아니라 '기능 꺼짐'으로 구분
  • 초기 전송 1,855KB → 1,101KB
  • 시스템 탭 26개 섹션을 보는 주기로 3층 정리
  • 기능 스위치를 화면에서 켜고 끄기

운영

  • 실행 이력 보존 12일 → 90일
  • AdSense 필수 페이지 확인 (읽기 전용)

검증

  • 테스트 695건 통과
  • 헤드리스 브라우저로 대시보드 주요 흐름 확인 (초기 로딩, 지연 로딩, 차트 실패, 스위치 토글, 신선도 판정)

병합 방식

merge commit으로 병합해야 합니다. squash하면 kevin-blog-image-studio/가 삭제됩니다.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69


Generated by Claude Code

github-actionsBotand others added 30 commits August 19, 2026 10:47
인수인계 문서 1번·3번 항목입니다.
1) index_priority가 gscProperty/site를 내보내지 않아, 대시보드 색인 패널의
`c.gscProperty || c.site`가 항상 빈 값이었습니다. 다음 색인 점검이 돌면
패널에서 어느 Search Console 속성을 열지 판단할 근거가 없어집니다.
gsc_indexing은 이미 검사에 성공한 속성을 gsc_property로 남기고 있으므로
(URL-프리픽스인지 sc-domain인지가 여기서만 확실해짐) 그 값을 그대로
넘기고, 없으면 site로 대체합니다.
현재 index_status.json에 이 키가 없는 것은 파일이 해당 변경 이전에
생성됐기 때문이며, 다음 점검부터 채워집니다.
2) docs/index (1).html 삭제 — 업로드 중 이름이 바뀌어 생긴 사본입니다.
docs/index.html과 바이트 단위로 동일한 것을 확인하고 지웠습니다.
테스트 3건 추가 (전체 396건 통과)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
인수인계 문서 7번 항목입니다.
docs/data/gsc.json의 sites에 blog2·blog3만 있고 blog1(www.hoguwhat.com)이
빠져 있었습니다. 커스텀 도메인은 Search Console에 도메인 속성(sc-domain:)으로
등록된 경우가 많아 URL-프리픽스 형식으로 조회하면 403이 나는데,
blog_analytics와 gsc_indexing에는 있는 재시도 폴백이 gsc_fetcher에만
없어서 403을 받고 그대로 포기했습니다.
색인 검사(gsc_indexing)에서는 blog1이 정상 조회되고 있어(7건),
문제가 이 모듈에 한정된다는 것도 확인했습니다.
- 403이면 sc-domain 형식으로 한 번 더 조회
- 두 형식 모두 실패할 때만 경고하고, 메시지에 속성 유형 확인을 안내
- 폴백 도입으로 죽은 코드가 된 URL 조립부 제거
- 테스트 7건 추가 (전체 403건 통과)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
6번(기존 발행글 주제 중복)은 조사 결과 실제 중복 콘텐츠가 아니었습니다.
blog1·blog2 발행글의 제목 완전 일치 0건, 키워드 중복 1건뿐이며 겹치는
것은 카테고리일 뿐입니다. 주제 이탈분 71건을 모두 비공개하면 blog2에
9건만 남아 콘텐츠 부족으로 오히려 AdSense 거절 위험이 커지므로,
사용자 판단으로 보류하고 색인 회복을 우선하기로 했습니다.
migrate_offtopic.py가 blog1 전용이고 주제 키워드가 옛 설정 기준으로
하드코딩돼 있다는 점도 함께 남겼습니다 — 그대로 실행하면 오분류합니다.
반증된 가설 표에 "주제 중복이 정책 문제" 항목을 추가했습니다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
워크플로 5개가 기본 브랜치에 반영돼 pending-cleanup(주 1회),
alert-watchdog(6시간마다) 스케줄이 실제로 동작하기 시작합니다.
공개 저장소에서 토큰을 교환하던 get-oauth-token.yml도 제거됐습니다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
실행 #40 결과를 읽다가 "제출 421 / 색인 0 = 0%"로 결론지을 뻔했습니다.
사이트맵 API의 contents[].indexed는 구글이 지원을 중단한 필드로 항상 0이며,
실제 색인 수가 아닙니다. 필드 이름이 indexedUrls면 다음 사람도 같은 오독을
하게 됩니다.
- indexedUrls → indexedUrlsDeprecated 로 이름 변경 (값 자체는 그대로 보존)
- sitemap 결과에 note 추가 — 데이터 옆에 경고가 함께 붙어 다니도록
- sitemap_state docstring에 근거와 대체 판단 방법(index_status.json) 명시
- 모듈 docstring 갱신: 첫 실행 결과 설정 문제가 아님이 확인됐고,
그럼에도 이 진단이 안전망으로 계속 필요한 이유를 남김
- HANDOFF에 제약 항목과 반증된 가설 한 줄 추가, 08-19 진단 결과 반영
(canWrite=true / blog1이 실제 sc-domain 속성 → gsc_fetcher 수정 검증됨)
- 테스트 2건 추가, 옛 이름을 쓰던 기존 테스트 1건 갱신 (전체 405건 통과)
대시보드는 이 숫자를 표시하지 않아 변경하지 않았습니다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
github-actionsBotand others added 26 commits August 23, 2026 15:44
adsense-policy-update 워크플로가 만든 파일을 계약 검사가 잡았습니다.
검증기·작성 프롬프트가 읽는 입력 파일이라 대시보드에 나올 이유가 없습니다.
이 파일이 생겼다는 것 자체가 빈 CLAUDE_MODEL 수정이 실제로 통했다는
증거이기도 합니다 — 그 전에는 이 워크플로의 Claude 호출이 400으로
전부 실패했습니다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
워크플로 커밋 목록에 post_details.json이 빠져 있던 동안 발행된 2편의
FAQ·출처가 버려졌습니다. 목록에는 "FAQ 3개" 배지가 남고 모달을 열면
비어 있는 상태였습니다.
원본은 logs/run_history.json에 남아 있었습니다 — 실행 이력은 별도 경로로
커밋되기 때문입니다. 그걸 대조해 되살렸습니다.
2026-08-23 FAQ 3 / 출처 5 9월 대형마트 할인정보 총정리…
2026-08-22 FAQ 3 / 출처 2 여수 밤바다 오션뷰 숙소…
검증: 전체 100건 중 배지와 내용이 어긋나는 글 0건
recover_post_details.py를 남깁니다. 일회성 스크립트로 때우면 다음에
같은 일이 생겼을 때 다시 처음부터 만들어야 합니다.
- 기본은 미리보기, --apply에서만 기록
- 제목과 키워드가 둘 다 맞아야 같은 글로 봅니다. 제목만 보면 재발행된
동명의 글과 섞입니다
- 원래 FAQ·출처가 없던 글은 대상이 아닙니다 (유실이 아니므로)
- 이력에도 없어 복구 불가한 건수를 따로 보고합니다
누락 자체는 test_workflow_pending_files.py가 막고, 저장소에 빈틈이
남아 있지 않은지는 새 테스트가 계속 확인합니다. 651건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
도구(inbound_linker)는 2026-08-21부터 정상 동작했습니다. 그런데 고아는
165건에서 172건으로 오히려 늘었습니다. 링크가 안 걸린 게 아니라 처리량이
모자랐습니다.
해소: 주 1회 × 10건 = 10건/주
생성: 발행 1건당 고아 1건 → 하루 1편이면 7건/주
순감: 3건/주 → 밀린 172건을 걷어내는 데 57주
내부 링크는 글을 쓸 때 "새 글 → 기존 글" 한 방향으로만 걸립니다. 그래서
모든 새 글은 아무도 가리키지 않는 고아로 태어납니다. 배치를 아무리 빨리
돌려도 발행이 계속되는 한 새 고아는 계속 생깁니다.
두 갈래로 고칩니다.
1. 발행 직후 그 글에 역방향 링크 (blog-run, 블로그 3개 각각)
진단(internal_link_audit)을 먼저 돌려 방금 올린 글을 고아 목록에 넣고,
inbound_linker --limit 5 --apply로 링크를 겁니다. 순서가 중요합니다 —
진단을 건너뛰면 새 글이 목록에 없어 그 글만 계속 고아로 남습니다.
발행을 막을 이유는 없으므로 실패해도 경고만 남깁니다.
산출물(internal_links·inbound_links)을 커밋 목록에 추가했습니다.
빠뜨리면 다음 실행이 낡은 고아 목록을 읽습니다.
2. 밀린 물량은 매일 배치로 (inbound-links, 주간 → 매일, 10 → 25건)
스케줄 실행에는 inputs가 없어 `inputs.limit || 'N'`의 N이 실제 값입니다.
입력란 default만 고치고 이쪽을 놓치면 조용히 옛 처리량으로 돌아가므로
둘을 맞추고 테스트로 고정했습니다.
→ 순감 25건/일, 밀린 172건은 약 7일. 이후 배치는 보조로 남습니다.
기대치에 대해: 같은 측정에서 색인 검사 20건 중 고아 9건과 링크 있는 글
11건이 똑같이 미색인이었습니다. 내부 링크를 만든다고 색인이 풀린다는 근거는
아직 없습니다 — 표본이 전부 미색인이라 인과를 판정할 수 없습니다. 사이트
구조상 정당한 개선으로 보고 진행하며, 색인 해결책으로 광고하지 않습니다.
이 사양이 되돌아가지 않도록 테스트 8건 추가. 659건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
전체 점검에서 author_profile.json이 100% 템플릿(`예: ISA 계좌`)이라
경험형 경로가 한 번도 켜진 적이 없다는 것을 확인했습니다. AdSense가 보는
E-E-A-T 중 Experience 축이 통째로 비어 있는 상태입니다.
① 안전 구멍부터 막았습니다
프롬프트는 경험이 매칭되면 "아래는 글쓴이의 실제 경험 자료입니다"라고
선언하고 1인칭을 허용합니다. 그런데 summary가 비어 있으면 그 선언 아래에
아무것도 못 넣은 채 허가만 나갑니다 — 가짜 체험담을 막으려던 장치가
거꾸로 뚫립니다. summary가 빈 항목은 매칭하지 않습니다.
② author_profile.json 구조를 실제 발행 키워드에 맞춰 정리
템플릿 예시를 걷어내고 4개 분야(금융·여행·골프·생활서비스)의 토픽과
대상 블로그를 채웠습니다. summary·detail은 비워 뒀습니다 — 실제 경험은
지어낼 수 없고, 비어 있는 동안은 매칭되지 않아 글에 영향이 없습니다.
③ author_profile_check.py — 조용한 실패를 눈에 보이게
이 파일은 비어 있어도 워크플로가 초록으로 끝나고 모든 글이 조사형으로
나갑니다. 그게 정상 동작이라 아무도 눈치채지 못합니다. 등록 현황과
"이 항목을 채우면 최근 글 몇 편이 경험형으로 바뀌는지"를 보여 줍니다.
판정은 _match_experience를 그대로 씁니다. 처음에 자체 부분문자열 매칭으로
셌다가 "골프"가 "골프장"과 맞는 것처럼 과대 보고했습니다(17편 → 실제 14편).
실제 매칭은 단어 단위이고, 규칙이 두 곳에 살면 반드시 어긋납니다.
현재 최근 40편 중 0편이 경험형이며, 4개 항목을 채우면 14편이 대상입니다.
민감정보 유입을 막는 검사(전화·주민번호·이메일 패턴)도 함께 넣었습니다.
테스트 12건 추가.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
회차와 글을 잇는 유일한 끈이 제목이었습니다. 그런데 final_editor가 생성 후
제목을 교정합니다(post_data["title"] = new_title). 그 순간 연결이 끊기고,
대시보드는 "매칭되는 포스트를 찾을 수 없습니다"만 띄웁니다 — 고장을 고친 게
아니라 고장 났을 때 띄울 메시지를 만들어 둔 상태였습니다.
점검 결과 pending_review 41편 중 38편이 검토 대기·보관·발행 목록·레지스트리
300건 어디에서도 제목으로 찾히지 않았습니다.
앞으로
- save_pending_posts가 배정한 id를 돌려줍니다. 건너뛴 항목도 빈 문자열로
자리를 지켜 호출부가 순서대로 짝지을 수 있습니다
- 시리즈 발행 흐름이 그 id를 회차에 기록합니다. 짝은 별도 목록으로 들고
다닙니다 — 회차에 임시 참조를 달면 루프 중간의 save_series가 그걸
series.json에 직렬화합니다
- 대시보드가 post_id를 먼저 보고, 없을 때만 제목·키워드로 폴백합니다
이미 끊긴 것
- series_relink.py: 제목 일치 → 앞부분 일치 → 그래도 없으면 status를
'lost'로. 'pending_review'로 두면 열 수 없는 일감이 검토 목록에 계속
남습니다. 왜 lost인지 사유도 함께 적어 나중에 다시 조사하지 않게 합니다
- 반영 결과: 앞부분 일치 3건 연결, 38건 lost
(done 15 / lost 38 / pending_review 3 / upload_failed 9)
탐색 범위를 넓히다 알게 된 것: posts.json은 최근 100편만 담고 있어 7월 회차를
못 봅니다. 레지스트리 300건까지 넣어 다시 확인한 뒤에야 "어디에도 없다"고
결론 냈습니다. 처음 범위로는 성급한 판정이 될 뻔했습니다.
제목 앞부분 매칭은 앞 14자 안에서 갈라지면 실패합니다. 그 한계를 테스트로
고정해 두었고, 그래서 앞으로는 id로 잇습니다.
테스트 9건 추가, 680건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
page_creator.py가 개인정보처리방침·소개 페이지를 만들 수 있는데, 그걸 부르는
진입점은 main.py --setup 하나뿐이고 어떤 워크플로도 그 경로를 실행하지
않습니다. 누군가 로컬에서 한 번 돌렸는지에 달려 있고, 돌렸더라도 나중에
지워졌는지 아무도 모릅니다. 심사에서 바로 걸리는 항목인데 시스템 어디에도
"있다/없다"가 기록되지 않았습니다.
- required_pages_check.py: 블로그별로 세 페이지(개인정보처리방침·소개·문의)
존재 여부만 확인합니다. 만들지 않습니다 — 발행 중인 블로그에 페이지를
얹는 건 사람이 정할 일이라, 없으면 만드는 명령만 알립니다
- 제목 별칭 매칭: 손으로 만들었다면 "개인정보 처리방침"처럼 띄어쓰기가
다를 수 있어 별칭으로도 찾습니다
- adsense-audit에 읽기 전용 단계 추가 + 리포트 커밋
- 대시보드 AdSense 감사 패널 위에 상태 한 줄
가장 신경 쓴 부분은 "모른다"와 "없다"를 섞지 않는 것입니다. 토큰 발급에
실패하면 확인 불가로 기록하고 누락으로 세지 않습니다 — 그러지 않으면
멀쩡한 블로그를 고치라고 하게 됩니다. 반대로 한 블로그도 확인하지 못했으면
ok로 끝내지 않습니다 (사이트맵에서 "한 건도 못 보냈는데 실패 0이라 성공"으로
읽던 것과 같은 유형).
이 환경에서는 자격증명이 없어 실제 페이지 존재 여부를 확인하지 못했습니다.
현재 리포트는 unknown 3 / ok false이며, 워크플로 첫 실행에서 실제 상태가
나옵니다.
헤드리스로 세 가지 표시(확인 불가·누락·정상)를 모두 확인했습니다.
테스트 8건 추가, 688건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
지금까지 스위치는 화면에 상태만 보여 줬고, 바꾸려면 저장소 파일을 직접
고쳐야 했습니다. 이제 버튼으로 바꿉니다.
동작
- GitHub API로 blog-automation/feature_flags.json에 커밋합니다
(쿠팡 링크 저장과 같은 방식 — 대시보드는 정적 페이지라 파일을 직접
고치지 못합니다). 러너가 읽는 그 파일이 원본이고, docs/data 쪽은 표시용
사본입니다
- 저장 전에 최신 내용을 받아 그 위에 한 항목만 바꿉니다. 화면의 사본으로
통째로 덮어쓰면 그 사이 다른 곳에서 바뀐 스위치를 되돌립니다
- PAT가 없으면 버튼을 비활성화하고 어디서 설정하는지 안내합니다
- 표에 없는 새 스위치도 이름 그대로 화면에 나옵니다
social_content 스위치 신설 + 배선
SOCIAL_AUTO_PUBLISH=false여도 콘텐츠는 매번 만들고 게시만 건너뛰었습니다.
실제로 50건 중 39건이 전부 skipped이고 게시는 0건인데, 생성에 드는 Claude
호출과 356KB는 계속 나갔습니다. "게시하지 않는다"와 "만들지도 않는다"는
다른 결정이라 스위치를 따로 뒀고, 확인을 생성 앞에 뒀습니다 — 만들고 나서
버리면 비용은 이미 나간 뒤입니다.
기본값은 꺼짐입니다. 켤 생각이 없는 기능을 계속 돌리고 있었으므로,
대시보드에서 언제든 다시 켤 수 있게 된 지금이 끄기 적절한 시점입니다.
헤드리스 검증
PAT 없음: 버튼 2개 전부 비활성 + 안내 문구
PAT 있음: GET → PUT 순서로 호출, 커밋될 내용에 화면에 없던
other_flag:true가 그대로 보존됨 (덮어쓰기 아님), UI 즉시 갱신
테스트 5건 추가 (스위치를 아무도 안 읽는 장식으로 두지 않도록 main.py가
실제로 확인하는지, 확인이 생성보다 먼저인지, 화면 이름이 빠진 스위치가
없는지 포함). 693건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
소셜 콘텐츠 생성을 끈 직후에 생기는 문제 두 가지를 함께 처리합니다.
기능을 끄는 것만으로는 끝이 아니라, 끈 뒤의 화면도 정직해야 합니다.
① 신선도 패널이 꺼진 기능을 '중단'으로 오인
social.json은 이제 안 늙는 게 정상인데, 기대 주기(24시간)를 넘기면
빨간 '⚠️ 중단'으로 떴을 겁니다. 일부러 끈 기능과 진짜 고장이 같은
색으로 보이면 경고가 의미를 잃습니다.
- SOURCE_META 항목에 그 파일을 만드는 기능 스위치를 적을 수 있게
했습니다: ['소셜 콘텐츠', 24, 'social_content']
- 스위치가 꺼져 있으면 '⏸ 기능 꺼짐'(회색)으로 표시하고 지연 집계에서
제외합니다. 요약에는 "기능 꺼짐 N개"로 따로 셉니다
② 소셜 탭이 옛 기록을 현재처럼 보여 줌
목록에 50건이 그대로 남아 있어, 안내가 없으면 여전히 돌아가는 줄
압니다. 생성이 꺼져 있을 때만 뜨는 안내를 넣었습니다 — 몇 건이
언제까지의 기록인지, 다시 켜려면 어디로 가는지까지.
스위치 이름 오타로 연결이 조용히 끊기면 ①이 원래대로 돌아가므로,
SOURCE_META가 가리키는 스위치가 실제로 있는지 검사합니다. 오타를 넣어
검사가 잡는 것도 확인했습니다.
헤드리스 확인
꺼짐: '⏸ 기능 꺼짐' + "1개 넘김 · 기능 꺼짐 1개" + 소셜 탭 안내 표시
켜짐: 같은 파일이 '⚠️ 30일 전'으로 지연 집계에 포함, 안내 숨김
테스트 2건 추가, 695건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
@Kevinlee7250
Kevinlee7250 marked this pull request as ready for review August 24, 2026 04:26
@Kevinlee7250
Kevinlee7250 merged commit 67cab1a into latestAug 24, 2026
3 checks passed
github-actionsBot added a commit that referenced this pull request Aug 25, 2026
github-actionsBot added a commit that referenced this pull request Aug 26, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@Kevinlee7250@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

블로그 자동화 작업 브랜치를 기본 브랜치에 병합 - #25

Merged
Kevinlee7250 merged 1122 commits into
latestfrom
claude/blog-automation-system-KAbtE
Aug 24, 2026
Merged

블로그 자동화 작업 브랜치를 기본 브랜치에 병합#25
Kevinlee7250 merged 1122 commits into
latestfrom
claude/blog-automation-system-KAbtE

Conversation

@Kevinlee7250

Copy link
Copy Markdown
Owner

지금까지 워크플로 정의만 latest에 동기화하고(#22·#23·#24) 실행 코드는 작업 브랜치에 두었습니다. 러너가 작업 브랜치를 체크아웃해 쓰기 때문에 동작에는 문제가 없었지만, 두 브랜치가 1,122커밋 갈라진 상태입니다. 이번에 코드 전체를 기본 브랜치로 가져옵니다.

병합 전 확인한 것

로컬에서 실제로 머지를 돌려 결과를 확인했습니다.

Automatic merge went well
충돌 없음
추가 318 · 수정 67 · 삭제 1

kevin-blog-image-studio/ 보존됨. 이 디렉터리는 latest에만 있고 작업 브랜치에는 없어서 단순 diff로는 "28개 파일 삭제"로 보입니다. 공통 조상(2026-07-23)에도 없고 latest에서 나중에 추가된 것이라, 일반 머지에서는 그대로 남습니다 — 시뮬레이션으로 확인했습니다. 이 때문에 squash가 아니라 merge commit이 필요합니다.

삭제되는 파일 1개: .github/oauth-token-trigger — 보안상 제거된 OAuth 교환 워크플로의 잔재입니다. 공개 저장소에 인증 코드 조각이 남아 있던 것으로, 작업 브랜치가 4a39c349에서 이미 지웠습니다. 병합하면 기본 브랜치에서도 사라집니다.

주요 내용

이번 세션에서 고친 것들입니다.

조용한 실패 차단

  • CLAUDE_MODEL 시크릿이 모든 Claude 호출을 400으로 실패시키던 문제
  • 검토 대기 저장소 분리 (목록·본문·보관) + 워크플로 커밋 누락 검사
  • 대시보드 데이터 계약 검사 6종 — 죽은 연결·표시되지 않는 산출물·신선도 누락을 자동으로 잡습니다
  • 차트 라이브러리 로드 실패가 대시보드 전체를 죽이던 문제 (패널별 격리)

연결이 끊겨 있던 곳

  • 시리즈 회차 ↔ 글을 제목 대신 post_id로 연결. final_editor가 제목을 교정하는 순간 끊기던 구조였고, 41편 중 38편이 이미 미아였습니다
  • 저자 경험 자료가 비었는데 1인칭만 허용되던 구멍 차단

대시보드

  • 데이터 신선도 표시 (24개 파일 + 18개 패널 배지), 꺼진 기능은 '중단'이 아니라 '기능 꺼짐'으로 구분
  • 초기 전송 1,855KB → 1,101KB
  • 시스템 탭 26개 섹션을 보는 주기로 3층 정리
  • 기능 스위치를 화면에서 켜고 끄기

운영

  • 실행 이력 보존 12일 → 90일
  • AdSense 필수 페이지 확인 (읽기 전용)

검증

  • 테스트 695건 통과
  • 헤드리스 브라우저로 대시보드 주요 흐름 확인 (초기 로딩, 지연 로딩, 차트 실패, 스위치 토글, 신선도 판정)

병합 방식

merge commit으로 병합해야 합니다. squash하면 kevin-blog-image-studio/가 삭제됩니다.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69


Generated by Claude Code

github-actionsBotand others added 30 commits August 19, 2026 10:47
인수인계 문서 1번·3번 항목입니다.
1) index_priority가 gscProperty/site를 내보내지 않아, 대시보드 색인 패널의
`c.gscProperty || c.site`가 항상 빈 값이었습니다. 다음 색인 점검이 돌면
패널에서 어느 Search Console 속성을 열지 판단할 근거가 없어집니다.
gsc_indexing은 이미 검사에 성공한 속성을 gsc_property로 남기고 있으므로
(URL-프리픽스인지 sc-domain인지가 여기서만 확실해짐) 그 값을 그대로
넘기고, 없으면 site로 대체합니다.
현재 index_status.json에 이 키가 없는 것은 파일이 해당 변경 이전에
생성됐기 때문이며, 다음 점검부터 채워집니다.
2) docs/index (1).html 삭제 — 업로드 중 이름이 바뀌어 생긴 사본입니다.
docs/index.html과 바이트 단위로 동일한 것을 확인하고 지웠습니다.
테스트 3건 추가 (전체 396건 통과)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
인수인계 문서 7번 항목입니다.
docs/data/gsc.json의 sites에 blog2·blog3만 있고 blog1(www.hoguwhat.com)이
빠져 있었습니다. 커스텀 도메인은 Search Console에 도메인 속성(sc-domain:)으로
등록된 경우가 많아 URL-프리픽스 형식으로 조회하면 403이 나는데,
blog_analytics와 gsc_indexing에는 있는 재시도 폴백이 gsc_fetcher에만
없어서 403을 받고 그대로 포기했습니다.
색인 검사(gsc_indexing)에서는 blog1이 정상 조회되고 있어(7건),
문제가 이 모듈에 한정된다는 것도 확인했습니다.
- 403이면 sc-domain 형식으로 한 번 더 조회
- 두 형식 모두 실패할 때만 경고하고, 메시지에 속성 유형 확인을 안내
- 폴백 도입으로 죽은 코드가 된 URL 조립부 제거
- 테스트 7건 추가 (전체 403건 통과)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
6번(기존 발행글 주제 중복)은 조사 결과 실제 중복 콘텐츠가 아니었습니다.
blog1·blog2 발행글의 제목 완전 일치 0건, 키워드 중복 1건뿐이며 겹치는
것은 카테고리일 뿐입니다. 주제 이탈분 71건을 모두 비공개하면 blog2에
9건만 남아 콘텐츠 부족으로 오히려 AdSense 거절 위험이 커지므로,
사용자 판단으로 보류하고 색인 회복을 우선하기로 했습니다.
migrate_offtopic.py가 blog1 전용이고 주제 키워드가 옛 설정 기준으로
하드코딩돼 있다는 점도 함께 남겼습니다 — 그대로 실행하면 오분류합니다.
반증된 가설 표에 "주제 중복이 정책 문제" 항목을 추가했습니다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
워크플로 5개가 기본 브랜치에 반영돼 pending-cleanup(주 1회),
alert-watchdog(6시간마다) 스케줄이 실제로 동작하기 시작합니다.
공개 저장소에서 토큰을 교환하던 get-oauth-token.yml도 제거됐습니다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
실행 #40 결과를 읽다가 "제출 421 / 색인 0 = 0%"로 결론지을 뻔했습니다.
사이트맵 API의 contents[].indexed는 구글이 지원을 중단한 필드로 항상 0이며,
실제 색인 수가 아닙니다. 필드 이름이 indexedUrls면 다음 사람도 같은 오독을
하게 됩니다.
- indexedUrls → indexedUrlsDeprecated 로 이름 변경 (값 자체는 그대로 보존)
- sitemap 결과에 note 추가 — 데이터 옆에 경고가 함께 붙어 다니도록
- sitemap_state docstring에 근거와 대체 판단 방법(index_status.json) 명시
- 모듈 docstring 갱신: 첫 실행 결과 설정 문제가 아님이 확인됐고,
그럼에도 이 진단이 안전망으로 계속 필요한 이유를 남김
- HANDOFF에 제약 항목과 반증된 가설 한 줄 추가, 08-19 진단 결과 반영
(canWrite=true / blog1이 실제 sc-domain 속성 → gsc_fetcher 수정 검증됨)
- 테스트 2건 추가, 옛 이름을 쓰던 기존 테스트 1건 갱신 (전체 405건 통과)
대시보드는 이 숫자를 표시하지 않아 변경하지 않았습니다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
github-actionsBotand others added 26 commits August 23, 2026 15:44
adsense-policy-update 워크플로가 만든 파일을 계약 검사가 잡았습니다.
검증기·작성 프롬프트가 읽는 입력 파일이라 대시보드에 나올 이유가 없습니다.
이 파일이 생겼다는 것 자체가 빈 CLAUDE_MODEL 수정이 실제로 통했다는
증거이기도 합니다 — 그 전에는 이 워크플로의 Claude 호출이 400으로
전부 실패했습니다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
워크플로 커밋 목록에 post_details.json이 빠져 있던 동안 발행된 2편의
FAQ·출처가 버려졌습니다. 목록에는 "FAQ 3개" 배지가 남고 모달을 열면
비어 있는 상태였습니다.
원본은 logs/run_history.json에 남아 있었습니다 — 실행 이력은 별도 경로로
커밋되기 때문입니다. 그걸 대조해 되살렸습니다.
2026-08-23 FAQ 3 / 출처 5 9월 대형마트 할인정보 총정리…
2026-08-22 FAQ 3 / 출처 2 여수 밤바다 오션뷰 숙소…
검증: 전체 100건 중 배지와 내용이 어긋나는 글 0건
recover_post_details.py를 남깁니다. 일회성 스크립트로 때우면 다음에
같은 일이 생겼을 때 다시 처음부터 만들어야 합니다.
- 기본은 미리보기, --apply에서만 기록
- 제목과 키워드가 둘 다 맞아야 같은 글로 봅니다. 제목만 보면 재발행된
동명의 글과 섞입니다
- 원래 FAQ·출처가 없던 글은 대상이 아닙니다 (유실이 아니므로)
- 이력에도 없어 복구 불가한 건수를 따로 보고합니다
누락 자체는 test_workflow_pending_files.py가 막고, 저장소에 빈틈이
남아 있지 않은지는 새 테스트가 계속 확인합니다. 651건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
도구(inbound_linker)는 2026-08-21부터 정상 동작했습니다. 그런데 고아는
165건에서 172건으로 오히려 늘었습니다. 링크가 안 걸린 게 아니라 처리량이
모자랐습니다.
해소: 주 1회 × 10건 = 10건/주
생성: 발행 1건당 고아 1건 → 하루 1편이면 7건/주
순감: 3건/주 → 밀린 172건을 걷어내는 데 57주
내부 링크는 글을 쓸 때 "새 글 → 기존 글" 한 방향으로만 걸립니다. 그래서
모든 새 글은 아무도 가리키지 않는 고아로 태어납니다. 배치를 아무리 빨리
돌려도 발행이 계속되는 한 새 고아는 계속 생깁니다.
두 갈래로 고칩니다.
1. 발행 직후 그 글에 역방향 링크 (blog-run, 블로그 3개 각각)
진단(internal_link_audit)을 먼저 돌려 방금 올린 글을 고아 목록에 넣고,
inbound_linker --limit 5 --apply로 링크를 겁니다. 순서가 중요합니다 —
진단을 건너뛰면 새 글이 목록에 없어 그 글만 계속 고아로 남습니다.
발행을 막을 이유는 없으므로 실패해도 경고만 남깁니다.
산출물(internal_links·inbound_links)을 커밋 목록에 추가했습니다.
빠뜨리면 다음 실행이 낡은 고아 목록을 읽습니다.
2. 밀린 물량은 매일 배치로 (inbound-links, 주간 → 매일, 10 → 25건)
스케줄 실행에는 inputs가 없어 `inputs.limit || 'N'`의 N이 실제 값입니다.
입력란 default만 고치고 이쪽을 놓치면 조용히 옛 처리량으로 돌아가므로
둘을 맞추고 테스트로 고정했습니다.
→ 순감 25건/일, 밀린 172건은 약 7일. 이후 배치는 보조로 남습니다.
기대치에 대해: 같은 측정에서 색인 검사 20건 중 고아 9건과 링크 있는 글
11건이 똑같이 미색인이었습니다. 내부 링크를 만든다고 색인이 풀린다는 근거는
아직 없습니다 — 표본이 전부 미색인이라 인과를 판정할 수 없습니다. 사이트
구조상 정당한 개선으로 보고 진행하며, 색인 해결책으로 광고하지 않습니다.
이 사양이 되돌아가지 않도록 테스트 8건 추가. 659건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
전체 점검에서 author_profile.json이 100% 템플릿(`예: ISA 계좌`)이라
경험형 경로가 한 번도 켜진 적이 없다는 것을 확인했습니다. AdSense가 보는
E-E-A-T 중 Experience 축이 통째로 비어 있는 상태입니다.
① 안전 구멍부터 막았습니다
프롬프트는 경험이 매칭되면 "아래는 글쓴이의 실제 경험 자료입니다"라고
선언하고 1인칭을 허용합니다. 그런데 summary가 비어 있으면 그 선언 아래에
아무것도 못 넣은 채 허가만 나갑니다 — 가짜 체험담을 막으려던 장치가
거꾸로 뚫립니다. summary가 빈 항목은 매칭하지 않습니다.
② author_profile.json 구조를 실제 발행 키워드에 맞춰 정리
템플릿 예시를 걷어내고 4개 분야(금융·여행·골프·생활서비스)의 토픽과
대상 블로그를 채웠습니다. summary·detail은 비워 뒀습니다 — 실제 경험은
지어낼 수 없고, 비어 있는 동안은 매칭되지 않아 글에 영향이 없습니다.
③ author_profile_check.py — 조용한 실패를 눈에 보이게
이 파일은 비어 있어도 워크플로가 초록으로 끝나고 모든 글이 조사형으로
나갑니다. 그게 정상 동작이라 아무도 눈치채지 못합니다. 등록 현황과
"이 항목을 채우면 최근 글 몇 편이 경험형으로 바뀌는지"를 보여 줍니다.
판정은 _match_experience를 그대로 씁니다. 처음에 자체 부분문자열 매칭으로
셌다가 "골프"가 "골프장"과 맞는 것처럼 과대 보고했습니다(17편 → 실제 14편).
실제 매칭은 단어 단위이고, 규칙이 두 곳에 살면 반드시 어긋납니다.
현재 최근 40편 중 0편이 경험형이며, 4개 항목을 채우면 14편이 대상입니다.
민감정보 유입을 막는 검사(전화·주민번호·이메일 패턴)도 함께 넣었습니다.
테스트 12건 추가.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
회차와 글을 잇는 유일한 끈이 제목이었습니다. 그런데 final_editor가 생성 후
제목을 교정합니다(post_data["title"] = new_title). 그 순간 연결이 끊기고,
대시보드는 "매칭되는 포스트를 찾을 수 없습니다"만 띄웁니다 — 고장을 고친 게
아니라 고장 났을 때 띄울 메시지를 만들어 둔 상태였습니다.
점검 결과 pending_review 41편 중 38편이 검토 대기·보관·발행 목록·레지스트리
300건 어디에서도 제목으로 찾히지 않았습니다.
앞으로
- save_pending_posts가 배정한 id를 돌려줍니다. 건너뛴 항목도 빈 문자열로
자리를 지켜 호출부가 순서대로 짝지을 수 있습니다
- 시리즈 발행 흐름이 그 id를 회차에 기록합니다. 짝은 별도 목록으로 들고
다닙니다 — 회차에 임시 참조를 달면 루프 중간의 save_series가 그걸
series.json에 직렬화합니다
- 대시보드가 post_id를 먼저 보고, 없을 때만 제목·키워드로 폴백합니다
이미 끊긴 것
- series_relink.py: 제목 일치 → 앞부분 일치 → 그래도 없으면 status를
'lost'로. 'pending_review'로 두면 열 수 없는 일감이 검토 목록에 계속
남습니다. 왜 lost인지 사유도 함께 적어 나중에 다시 조사하지 않게 합니다
- 반영 결과: 앞부분 일치 3건 연결, 38건 lost
(done 15 / lost 38 / pending_review 3 / upload_failed 9)
탐색 범위를 넓히다 알게 된 것: posts.json은 최근 100편만 담고 있어 7월 회차를
못 봅니다. 레지스트리 300건까지 넣어 다시 확인한 뒤에야 "어디에도 없다"고
결론 냈습니다. 처음 범위로는 성급한 판정이 될 뻔했습니다.
제목 앞부분 매칭은 앞 14자 안에서 갈라지면 실패합니다. 그 한계를 테스트로
고정해 두었고, 그래서 앞으로는 id로 잇습니다.
테스트 9건 추가, 680건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
page_creator.py가 개인정보처리방침·소개 페이지를 만들 수 있는데, 그걸 부르는
진입점은 main.py --setup 하나뿐이고 어떤 워크플로도 그 경로를 실행하지
않습니다. 누군가 로컬에서 한 번 돌렸는지에 달려 있고, 돌렸더라도 나중에
지워졌는지 아무도 모릅니다. 심사에서 바로 걸리는 항목인데 시스템 어디에도
"있다/없다"가 기록되지 않았습니다.
- required_pages_check.py: 블로그별로 세 페이지(개인정보처리방침·소개·문의)
존재 여부만 확인합니다. 만들지 않습니다 — 발행 중인 블로그에 페이지를
얹는 건 사람이 정할 일이라, 없으면 만드는 명령만 알립니다
- 제목 별칭 매칭: 손으로 만들었다면 "개인정보 처리방침"처럼 띄어쓰기가
다를 수 있어 별칭으로도 찾습니다
- adsense-audit에 읽기 전용 단계 추가 + 리포트 커밋
- 대시보드 AdSense 감사 패널 위에 상태 한 줄
가장 신경 쓴 부분은 "모른다"와 "없다"를 섞지 않는 것입니다. 토큰 발급에
실패하면 확인 불가로 기록하고 누락으로 세지 않습니다 — 그러지 않으면
멀쩡한 블로그를 고치라고 하게 됩니다. 반대로 한 블로그도 확인하지 못했으면
ok로 끝내지 않습니다 (사이트맵에서 "한 건도 못 보냈는데 실패 0이라 성공"으로
읽던 것과 같은 유형).
이 환경에서는 자격증명이 없어 실제 페이지 존재 여부를 확인하지 못했습니다.
현재 리포트는 unknown 3 / ok false이며, 워크플로 첫 실행에서 실제 상태가
나옵니다.
헤드리스로 세 가지 표시(확인 불가·누락·정상)를 모두 확인했습니다.
테스트 8건 추가, 688건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
지금까지 스위치는 화면에 상태만 보여 줬고, 바꾸려면 저장소 파일을 직접
고쳐야 했습니다. 이제 버튼으로 바꿉니다.
동작
- GitHub API로 blog-automation/feature_flags.json에 커밋합니다
(쿠팡 링크 저장과 같은 방식 — 대시보드는 정적 페이지라 파일을 직접
고치지 못합니다). 러너가 읽는 그 파일이 원본이고, docs/data 쪽은 표시용
사본입니다
- 저장 전에 최신 내용을 받아 그 위에 한 항목만 바꿉니다. 화면의 사본으로
통째로 덮어쓰면 그 사이 다른 곳에서 바뀐 스위치를 되돌립니다
- PAT가 없으면 버튼을 비활성화하고 어디서 설정하는지 안내합니다
- 표에 없는 새 스위치도 이름 그대로 화면에 나옵니다
social_content 스위치 신설 + 배선
SOCIAL_AUTO_PUBLISH=false여도 콘텐츠는 매번 만들고 게시만 건너뛰었습니다.
실제로 50건 중 39건이 전부 skipped이고 게시는 0건인데, 생성에 드는 Claude
호출과 356KB는 계속 나갔습니다. "게시하지 않는다"와 "만들지도 않는다"는
다른 결정이라 스위치를 따로 뒀고, 확인을 생성 앞에 뒀습니다 — 만들고 나서
버리면 비용은 이미 나간 뒤입니다.
기본값은 꺼짐입니다. 켤 생각이 없는 기능을 계속 돌리고 있었으므로,
대시보드에서 언제든 다시 켤 수 있게 된 지금이 끄기 적절한 시점입니다.
헤드리스 검증
PAT 없음: 버튼 2개 전부 비활성 + 안내 문구
PAT 있음: GET → PUT 순서로 호출, 커밋될 내용에 화면에 없던
other_flag:true가 그대로 보존됨 (덮어쓰기 아님), UI 즉시 갱신
테스트 5건 추가 (스위치를 아무도 안 읽는 장식으로 두지 않도록 main.py가
실제로 확인하는지, 확인이 생성보다 먼저인지, 화면 이름이 빠진 스위치가
없는지 포함). 693건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
소셜 콘텐츠 생성을 끈 직후에 생기는 문제 두 가지를 함께 처리합니다.
기능을 끄는 것만으로는 끝이 아니라, 끈 뒤의 화면도 정직해야 합니다.
① 신선도 패널이 꺼진 기능을 '중단'으로 오인
social.json은 이제 안 늙는 게 정상인데, 기대 주기(24시간)를 넘기면
빨간 '⚠️ 중단'으로 떴을 겁니다. 일부러 끈 기능과 진짜 고장이 같은
색으로 보이면 경고가 의미를 잃습니다.
- SOURCE_META 항목에 그 파일을 만드는 기능 스위치를 적을 수 있게
했습니다: ['소셜 콘텐츠', 24, 'social_content']
- 스위치가 꺼져 있으면 '⏸ 기능 꺼짐'(회색)으로 표시하고 지연 집계에서
제외합니다. 요약에는 "기능 꺼짐 N개"로 따로 셉니다
② 소셜 탭이 옛 기록을 현재처럼 보여 줌
목록에 50건이 그대로 남아 있어, 안내가 없으면 여전히 돌아가는 줄
압니다. 생성이 꺼져 있을 때만 뜨는 안내를 넣었습니다 — 몇 건이
언제까지의 기록인지, 다시 켜려면 어디로 가는지까지.
스위치 이름 오타로 연결이 조용히 끊기면 ①이 원래대로 돌아가므로,
SOURCE_META가 가리키는 스위치가 실제로 있는지 검사합니다. 오타를 넣어
검사가 잡는 것도 확인했습니다.
헤드리스 확인
꺼짐: '⏸ 기능 꺼짐' + "1개 넘김 · 기능 꺼짐 1개" + 소셜 탭 안내 표시
켜짐: 같은 파일이 '⚠️ 30일 전'으로 지연 집계에 포함, 안내 숨김
테스트 2건 추가, 695건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
@Kevinlee7250
Kevinlee7250 marked this pull request as ready for review August 24, 2026 04:26
@Kevinlee7250
Kevinlee7250 merged commit 67cab1a into latestAug 24, 2026
3 checks passed
github-actionsBot added a commit that referenced this pull request Aug 25, 2026
github-actionsBot added a commit that referenced this pull request Aug 26, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@Kevinlee7250@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

블로그 자동화 작업 브랜치를 기본 브랜치에 병합 - #25

Merged
Kevinlee7250 merged 1122 commits into
latestfrom
claude/blog-automation-system-KAbtE
Aug 24, 2026
Merged

블로그 자동화 작업 브랜치를 기본 브랜치에 병합#25
Kevinlee7250 merged 1122 commits into
latestfrom
claude/blog-automation-system-KAbtE

Conversation

@Kevinlee7250

Copy link
Copy Markdown
Owner

지금까지 워크플로 정의만 latest에 동기화하고(#22·#23·#24) 실행 코드는 작업 브랜치에 두었습니다. 러너가 작업 브랜치를 체크아웃해 쓰기 때문에 동작에는 문제가 없었지만, 두 브랜치가 1,122커밋 갈라진 상태입니다. 이번에 코드 전체를 기본 브랜치로 가져옵니다.

병합 전 확인한 것

로컬에서 실제로 머지를 돌려 결과를 확인했습니다.

Automatic merge went well
충돌 없음
추가 318 · 수정 67 · 삭제 1

kevin-blog-image-studio/ 보존됨. 이 디렉터리는 latest에만 있고 작업 브랜치에는 없어서 단순 diff로는 "28개 파일 삭제"로 보입니다. 공통 조상(2026-07-23)에도 없고 latest에서 나중에 추가된 것이라, 일반 머지에서는 그대로 남습니다 — 시뮬레이션으로 확인했습니다. 이 때문에 squash가 아니라 merge commit이 필요합니다.

삭제되는 파일 1개: .github/oauth-token-trigger — 보안상 제거된 OAuth 교환 워크플로의 잔재입니다. 공개 저장소에 인증 코드 조각이 남아 있던 것으로, 작업 브랜치가 4a39c349에서 이미 지웠습니다. 병합하면 기본 브랜치에서도 사라집니다.

주요 내용

이번 세션에서 고친 것들입니다.

조용한 실패 차단

  • CLAUDE_MODEL 시크릿이 모든 Claude 호출을 400으로 실패시키던 문제
  • 검토 대기 저장소 분리 (목록·본문·보관) + 워크플로 커밋 누락 검사
  • 대시보드 데이터 계약 검사 6종 — 죽은 연결·표시되지 않는 산출물·신선도 누락을 자동으로 잡습니다
  • 차트 라이브러리 로드 실패가 대시보드 전체를 죽이던 문제 (패널별 격리)

연결이 끊겨 있던 곳

  • 시리즈 회차 ↔ 글을 제목 대신 post_id로 연결. final_editor가 제목을 교정하는 순간 끊기던 구조였고, 41편 중 38편이 이미 미아였습니다
  • 저자 경험 자료가 비었는데 1인칭만 허용되던 구멍 차단

대시보드

  • 데이터 신선도 표시 (24개 파일 + 18개 패널 배지), 꺼진 기능은 '중단'이 아니라 '기능 꺼짐'으로 구분
  • 초기 전송 1,855KB → 1,101KB
  • 시스템 탭 26개 섹션을 보는 주기로 3층 정리
  • 기능 스위치를 화면에서 켜고 끄기

운영

  • 실행 이력 보존 12일 → 90일
  • AdSense 필수 페이지 확인 (읽기 전용)

검증

  • 테스트 695건 통과
  • 헤드리스 브라우저로 대시보드 주요 흐름 확인 (초기 로딩, 지연 로딩, 차트 실패, 스위치 토글, 신선도 판정)

병합 방식

merge commit으로 병합해야 합니다. squash하면 kevin-blog-image-studio/가 삭제됩니다.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69


Generated by Claude Code

github-actionsBotand others added 30 commits August 19, 2026 10:47
인수인계 문서 1번·3번 항목입니다.
1) index_priority가 gscProperty/site를 내보내지 않아, 대시보드 색인 패널의
`c.gscProperty || c.site`가 항상 빈 값이었습니다. 다음 색인 점검이 돌면
패널에서 어느 Search Console 속성을 열지 판단할 근거가 없어집니다.
gsc_indexing은 이미 검사에 성공한 속성을 gsc_property로 남기고 있으므로
(URL-프리픽스인지 sc-domain인지가 여기서만 확실해짐) 그 값을 그대로
넘기고, 없으면 site로 대체합니다.
현재 index_status.json에 이 키가 없는 것은 파일이 해당 변경 이전에
생성됐기 때문이며, 다음 점검부터 채워집니다.
2) docs/index (1).html 삭제 — 업로드 중 이름이 바뀌어 생긴 사본입니다.
docs/index.html과 바이트 단위로 동일한 것을 확인하고 지웠습니다.
테스트 3건 추가 (전체 396건 통과)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
인수인계 문서 7번 항목입니다.
docs/data/gsc.json의 sites에 blog2·blog3만 있고 blog1(www.hoguwhat.com)이
빠져 있었습니다. 커스텀 도메인은 Search Console에 도메인 속성(sc-domain:)으로
등록된 경우가 많아 URL-프리픽스 형식으로 조회하면 403이 나는데,
blog_analytics와 gsc_indexing에는 있는 재시도 폴백이 gsc_fetcher에만
없어서 403을 받고 그대로 포기했습니다.
색인 검사(gsc_indexing)에서는 blog1이 정상 조회되고 있어(7건),
문제가 이 모듈에 한정된다는 것도 확인했습니다.
- 403이면 sc-domain 형식으로 한 번 더 조회
- 두 형식 모두 실패할 때만 경고하고, 메시지에 속성 유형 확인을 안내
- 폴백 도입으로 죽은 코드가 된 URL 조립부 제거
- 테스트 7건 추가 (전체 403건 통과)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
6번(기존 발행글 주제 중복)은 조사 결과 실제 중복 콘텐츠가 아니었습니다.
blog1·blog2 발행글의 제목 완전 일치 0건, 키워드 중복 1건뿐이며 겹치는
것은 카테고리일 뿐입니다. 주제 이탈분 71건을 모두 비공개하면 blog2에
9건만 남아 콘텐츠 부족으로 오히려 AdSense 거절 위험이 커지므로,
사용자 판단으로 보류하고 색인 회복을 우선하기로 했습니다.
migrate_offtopic.py가 blog1 전용이고 주제 키워드가 옛 설정 기준으로
하드코딩돼 있다는 점도 함께 남겼습니다 — 그대로 실행하면 오분류합니다.
반증된 가설 표에 "주제 중복이 정책 문제" 항목을 추가했습니다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
워크플로 5개가 기본 브랜치에 반영돼 pending-cleanup(주 1회),
alert-watchdog(6시간마다) 스케줄이 실제로 동작하기 시작합니다.
공개 저장소에서 토큰을 교환하던 get-oauth-token.yml도 제거됐습니다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
실행 #40 결과를 읽다가 "제출 421 / 색인 0 = 0%"로 결론지을 뻔했습니다.
사이트맵 API의 contents[].indexed는 구글이 지원을 중단한 필드로 항상 0이며,
실제 색인 수가 아닙니다. 필드 이름이 indexedUrls면 다음 사람도 같은 오독을
하게 됩니다.
- indexedUrls → indexedUrlsDeprecated 로 이름 변경 (값 자체는 그대로 보존)
- sitemap 결과에 note 추가 — 데이터 옆에 경고가 함께 붙어 다니도록
- sitemap_state docstring에 근거와 대체 판단 방법(index_status.json) 명시
- 모듈 docstring 갱신: 첫 실행 결과 설정 문제가 아님이 확인됐고,
그럼에도 이 진단이 안전망으로 계속 필요한 이유를 남김
- HANDOFF에 제약 항목과 반증된 가설 한 줄 추가, 08-19 진단 결과 반영
(canWrite=true / blog1이 실제 sc-domain 속성 → gsc_fetcher 수정 검증됨)
- 테스트 2건 추가, 옛 이름을 쓰던 기존 테스트 1건 갱신 (전체 405건 통과)
대시보드는 이 숫자를 표시하지 않아 변경하지 않았습니다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
github-actionsBotand others added 26 commits August 23, 2026 15:44
adsense-policy-update 워크플로가 만든 파일을 계약 검사가 잡았습니다.
검증기·작성 프롬프트가 읽는 입력 파일이라 대시보드에 나올 이유가 없습니다.
이 파일이 생겼다는 것 자체가 빈 CLAUDE_MODEL 수정이 실제로 통했다는
증거이기도 합니다 — 그 전에는 이 워크플로의 Claude 호출이 400으로
전부 실패했습니다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
워크플로 커밋 목록에 post_details.json이 빠져 있던 동안 발행된 2편의
FAQ·출처가 버려졌습니다. 목록에는 "FAQ 3개" 배지가 남고 모달을 열면
비어 있는 상태였습니다.
원본은 logs/run_history.json에 남아 있었습니다 — 실행 이력은 별도 경로로
커밋되기 때문입니다. 그걸 대조해 되살렸습니다.
2026-08-23 FAQ 3 / 출처 5 9월 대형마트 할인정보 총정리…
2026-08-22 FAQ 3 / 출처 2 여수 밤바다 오션뷰 숙소…
검증: 전체 100건 중 배지와 내용이 어긋나는 글 0건
recover_post_details.py를 남깁니다. 일회성 스크립트로 때우면 다음에
같은 일이 생겼을 때 다시 처음부터 만들어야 합니다.
- 기본은 미리보기, --apply에서만 기록
- 제목과 키워드가 둘 다 맞아야 같은 글로 봅니다. 제목만 보면 재발행된
동명의 글과 섞입니다
- 원래 FAQ·출처가 없던 글은 대상이 아닙니다 (유실이 아니므로)
- 이력에도 없어 복구 불가한 건수를 따로 보고합니다
누락 자체는 test_workflow_pending_files.py가 막고, 저장소에 빈틈이
남아 있지 않은지는 새 테스트가 계속 확인합니다. 651건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
도구(inbound_linker)는 2026-08-21부터 정상 동작했습니다. 그런데 고아는
165건에서 172건으로 오히려 늘었습니다. 링크가 안 걸린 게 아니라 처리량이
모자랐습니다.
해소: 주 1회 × 10건 = 10건/주
생성: 발행 1건당 고아 1건 → 하루 1편이면 7건/주
순감: 3건/주 → 밀린 172건을 걷어내는 데 57주
내부 링크는 글을 쓸 때 "새 글 → 기존 글" 한 방향으로만 걸립니다. 그래서
모든 새 글은 아무도 가리키지 않는 고아로 태어납니다. 배치를 아무리 빨리
돌려도 발행이 계속되는 한 새 고아는 계속 생깁니다.
두 갈래로 고칩니다.
1. 발행 직후 그 글에 역방향 링크 (blog-run, 블로그 3개 각각)
진단(internal_link_audit)을 먼저 돌려 방금 올린 글을 고아 목록에 넣고,
inbound_linker --limit 5 --apply로 링크를 겁니다. 순서가 중요합니다 —
진단을 건너뛰면 새 글이 목록에 없어 그 글만 계속 고아로 남습니다.
발행을 막을 이유는 없으므로 실패해도 경고만 남깁니다.
산출물(internal_links·inbound_links)을 커밋 목록에 추가했습니다.
빠뜨리면 다음 실행이 낡은 고아 목록을 읽습니다.
2. 밀린 물량은 매일 배치로 (inbound-links, 주간 → 매일, 10 → 25건)
스케줄 실행에는 inputs가 없어 `inputs.limit || 'N'`의 N이 실제 값입니다.
입력란 default만 고치고 이쪽을 놓치면 조용히 옛 처리량으로 돌아가므로
둘을 맞추고 테스트로 고정했습니다.
→ 순감 25건/일, 밀린 172건은 약 7일. 이후 배치는 보조로 남습니다.
기대치에 대해: 같은 측정에서 색인 검사 20건 중 고아 9건과 링크 있는 글
11건이 똑같이 미색인이었습니다. 내부 링크를 만든다고 색인이 풀린다는 근거는
아직 없습니다 — 표본이 전부 미색인이라 인과를 판정할 수 없습니다. 사이트
구조상 정당한 개선으로 보고 진행하며, 색인 해결책으로 광고하지 않습니다.
이 사양이 되돌아가지 않도록 테스트 8건 추가. 659건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
전체 점검에서 author_profile.json이 100% 템플릿(`예: ISA 계좌`)이라
경험형 경로가 한 번도 켜진 적이 없다는 것을 확인했습니다. AdSense가 보는
E-E-A-T 중 Experience 축이 통째로 비어 있는 상태입니다.
① 안전 구멍부터 막았습니다
프롬프트는 경험이 매칭되면 "아래는 글쓴이의 실제 경험 자료입니다"라고
선언하고 1인칭을 허용합니다. 그런데 summary가 비어 있으면 그 선언 아래에
아무것도 못 넣은 채 허가만 나갑니다 — 가짜 체험담을 막으려던 장치가
거꾸로 뚫립니다. summary가 빈 항목은 매칭하지 않습니다.
② author_profile.json 구조를 실제 발행 키워드에 맞춰 정리
템플릿 예시를 걷어내고 4개 분야(금융·여행·골프·생활서비스)의 토픽과
대상 블로그를 채웠습니다. summary·detail은 비워 뒀습니다 — 실제 경험은
지어낼 수 없고, 비어 있는 동안은 매칭되지 않아 글에 영향이 없습니다.
③ author_profile_check.py — 조용한 실패를 눈에 보이게
이 파일은 비어 있어도 워크플로가 초록으로 끝나고 모든 글이 조사형으로
나갑니다. 그게 정상 동작이라 아무도 눈치채지 못합니다. 등록 현황과
"이 항목을 채우면 최근 글 몇 편이 경험형으로 바뀌는지"를 보여 줍니다.
판정은 _match_experience를 그대로 씁니다. 처음에 자체 부분문자열 매칭으로
셌다가 "골프"가 "골프장"과 맞는 것처럼 과대 보고했습니다(17편 → 실제 14편).
실제 매칭은 단어 단위이고, 규칙이 두 곳에 살면 반드시 어긋납니다.
현재 최근 40편 중 0편이 경험형이며, 4개 항목을 채우면 14편이 대상입니다.
민감정보 유입을 막는 검사(전화·주민번호·이메일 패턴)도 함께 넣었습니다.
테스트 12건 추가.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
회차와 글을 잇는 유일한 끈이 제목이었습니다. 그런데 final_editor가 생성 후
제목을 교정합니다(post_data["title"] = new_title). 그 순간 연결이 끊기고,
대시보드는 "매칭되는 포스트를 찾을 수 없습니다"만 띄웁니다 — 고장을 고친 게
아니라 고장 났을 때 띄울 메시지를 만들어 둔 상태였습니다.
점검 결과 pending_review 41편 중 38편이 검토 대기·보관·발행 목록·레지스트리
300건 어디에서도 제목으로 찾히지 않았습니다.
앞으로
- save_pending_posts가 배정한 id를 돌려줍니다. 건너뛴 항목도 빈 문자열로
자리를 지켜 호출부가 순서대로 짝지을 수 있습니다
- 시리즈 발행 흐름이 그 id를 회차에 기록합니다. 짝은 별도 목록으로 들고
다닙니다 — 회차에 임시 참조를 달면 루프 중간의 save_series가 그걸
series.json에 직렬화합니다
- 대시보드가 post_id를 먼저 보고, 없을 때만 제목·키워드로 폴백합니다
이미 끊긴 것
- series_relink.py: 제목 일치 → 앞부분 일치 → 그래도 없으면 status를
'lost'로. 'pending_review'로 두면 열 수 없는 일감이 검토 목록에 계속
남습니다. 왜 lost인지 사유도 함께 적어 나중에 다시 조사하지 않게 합니다
- 반영 결과: 앞부분 일치 3건 연결, 38건 lost
(done 15 / lost 38 / pending_review 3 / upload_failed 9)
탐색 범위를 넓히다 알게 된 것: posts.json은 최근 100편만 담고 있어 7월 회차를
못 봅니다. 레지스트리 300건까지 넣어 다시 확인한 뒤에야 "어디에도 없다"고
결론 냈습니다. 처음 범위로는 성급한 판정이 될 뻔했습니다.
제목 앞부분 매칭은 앞 14자 안에서 갈라지면 실패합니다. 그 한계를 테스트로
고정해 두었고, 그래서 앞으로는 id로 잇습니다.
테스트 9건 추가, 680건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
page_creator.py가 개인정보처리방침·소개 페이지를 만들 수 있는데, 그걸 부르는
진입점은 main.py --setup 하나뿐이고 어떤 워크플로도 그 경로를 실행하지
않습니다. 누군가 로컬에서 한 번 돌렸는지에 달려 있고, 돌렸더라도 나중에
지워졌는지 아무도 모릅니다. 심사에서 바로 걸리는 항목인데 시스템 어디에도
"있다/없다"가 기록되지 않았습니다.
- required_pages_check.py: 블로그별로 세 페이지(개인정보처리방침·소개·문의)
존재 여부만 확인합니다. 만들지 않습니다 — 발행 중인 블로그에 페이지를
얹는 건 사람이 정할 일이라, 없으면 만드는 명령만 알립니다
- 제목 별칭 매칭: 손으로 만들었다면 "개인정보 처리방침"처럼 띄어쓰기가
다를 수 있어 별칭으로도 찾습니다
- adsense-audit에 읽기 전용 단계 추가 + 리포트 커밋
- 대시보드 AdSense 감사 패널 위에 상태 한 줄
가장 신경 쓴 부분은 "모른다"와 "없다"를 섞지 않는 것입니다. 토큰 발급에
실패하면 확인 불가로 기록하고 누락으로 세지 않습니다 — 그러지 않으면
멀쩡한 블로그를 고치라고 하게 됩니다. 반대로 한 블로그도 확인하지 못했으면
ok로 끝내지 않습니다 (사이트맵에서 "한 건도 못 보냈는데 실패 0이라 성공"으로
읽던 것과 같은 유형).
이 환경에서는 자격증명이 없어 실제 페이지 존재 여부를 확인하지 못했습니다.
현재 리포트는 unknown 3 / ok false이며, 워크플로 첫 실행에서 실제 상태가
나옵니다.
헤드리스로 세 가지 표시(확인 불가·누락·정상)를 모두 확인했습니다.
테스트 8건 추가, 688건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
지금까지 스위치는 화면에 상태만 보여 줬고, 바꾸려면 저장소 파일을 직접
고쳐야 했습니다. 이제 버튼으로 바꿉니다.
동작
- GitHub API로 blog-automation/feature_flags.json에 커밋합니다
(쿠팡 링크 저장과 같은 방식 — 대시보드는 정적 페이지라 파일을 직접
고치지 못합니다). 러너가 읽는 그 파일이 원본이고, docs/data 쪽은 표시용
사본입니다
- 저장 전에 최신 내용을 받아 그 위에 한 항목만 바꿉니다. 화면의 사본으로
통째로 덮어쓰면 그 사이 다른 곳에서 바뀐 스위치를 되돌립니다
- PAT가 없으면 버튼을 비활성화하고 어디서 설정하는지 안내합니다
- 표에 없는 새 스위치도 이름 그대로 화면에 나옵니다
social_content 스위치 신설 + 배선
SOCIAL_AUTO_PUBLISH=false여도 콘텐츠는 매번 만들고 게시만 건너뛰었습니다.
실제로 50건 중 39건이 전부 skipped이고 게시는 0건인데, 생성에 드는 Claude
호출과 356KB는 계속 나갔습니다. "게시하지 않는다"와 "만들지도 않는다"는
다른 결정이라 스위치를 따로 뒀고, 확인을 생성 앞에 뒀습니다 — 만들고 나서
버리면 비용은 이미 나간 뒤입니다.
기본값은 꺼짐입니다. 켤 생각이 없는 기능을 계속 돌리고 있었으므로,
대시보드에서 언제든 다시 켤 수 있게 된 지금이 끄기 적절한 시점입니다.
헤드리스 검증
PAT 없음: 버튼 2개 전부 비활성 + 안내 문구
PAT 있음: GET → PUT 순서로 호출, 커밋될 내용에 화면에 없던
other_flag:true가 그대로 보존됨 (덮어쓰기 아님), UI 즉시 갱신
테스트 5건 추가 (스위치를 아무도 안 읽는 장식으로 두지 않도록 main.py가
실제로 확인하는지, 확인이 생성보다 먼저인지, 화면 이름이 빠진 스위치가
없는지 포함). 693건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
소셜 콘텐츠 생성을 끈 직후에 생기는 문제 두 가지를 함께 처리합니다.
기능을 끄는 것만으로는 끝이 아니라, 끈 뒤의 화면도 정직해야 합니다.
① 신선도 패널이 꺼진 기능을 '중단'으로 오인
social.json은 이제 안 늙는 게 정상인데, 기대 주기(24시간)를 넘기면
빨간 '⚠️ 중단'으로 떴을 겁니다. 일부러 끈 기능과 진짜 고장이 같은
색으로 보이면 경고가 의미를 잃습니다.
- SOURCE_META 항목에 그 파일을 만드는 기능 스위치를 적을 수 있게
했습니다: ['소셜 콘텐츠', 24, 'social_content']
- 스위치가 꺼져 있으면 '⏸ 기능 꺼짐'(회색)으로 표시하고 지연 집계에서
제외합니다. 요약에는 "기능 꺼짐 N개"로 따로 셉니다
② 소셜 탭이 옛 기록을 현재처럼 보여 줌
목록에 50건이 그대로 남아 있어, 안내가 없으면 여전히 돌아가는 줄
압니다. 생성이 꺼져 있을 때만 뜨는 안내를 넣었습니다 — 몇 건이
언제까지의 기록인지, 다시 켜려면 어디로 가는지까지.
스위치 이름 오타로 연결이 조용히 끊기면 ①이 원래대로 돌아가므로,
SOURCE_META가 가리키는 스위치가 실제로 있는지 검사합니다. 오타를 넣어
검사가 잡는 것도 확인했습니다.
헤드리스 확인
꺼짐: '⏸ 기능 꺼짐' + "1개 넘김 · 기능 꺼짐 1개" + 소셜 탭 안내 표시
켜짐: 같은 파일이 '⚠️ 30일 전'으로 지연 집계에 포함, 안내 숨김
테스트 2건 추가, 695건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
@Kevinlee7250
Kevinlee7250 marked this pull request as ready for review August 24, 2026 04:26
@Kevinlee7250
Kevinlee7250 merged commit 67cab1a into latestAug 24, 2026
3 checks passed
github-actionsBot added a commit that referenced this pull request Aug 25, 2026
github-actionsBot added a commit that referenced this pull request Aug 26, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@Kevinlee7250@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

블로그 자동화 작업 브랜치를 기본 브랜치에 병합 - #25

Merged
Kevinlee7250 merged 1122 commits into
latestfrom
claude/blog-automation-system-KAbtE
Aug 24, 2026
Merged

블로그 자동화 작업 브랜치를 기본 브랜치에 병합#25
Kevinlee7250 merged 1122 commits into
latestfrom
claude/blog-automation-system-KAbtE

Conversation

@Kevinlee7250

Copy link
Copy Markdown
Owner

지금까지 워크플로 정의만 latest에 동기화하고(#22·#23·#24) 실행 코드는 작업 브랜치에 두었습니다. 러너가 작업 브랜치를 체크아웃해 쓰기 때문에 동작에는 문제가 없었지만, 두 브랜치가 1,122커밋 갈라진 상태입니다. 이번에 코드 전체를 기본 브랜치로 가져옵니다.

병합 전 확인한 것

로컬에서 실제로 머지를 돌려 결과를 확인했습니다.

Automatic merge went well
충돌 없음
추가 318 · 수정 67 · 삭제 1

kevin-blog-image-studio/ 보존됨. 이 디렉터리는 latest에만 있고 작업 브랜치에는 없어서 단순 diff로는 "28개 파일 삭제"로 보입니다. 공통 조상(2026-07-23)에도 없고 latest에서 나중에 추가된 것이라, 일반 머지에서는 그대로 남습니다 — 시뮬레이션으로 확인했습니다. 이 때문에 squash가 아니라 merge commit이 필요합니다.

삭제되는 파일 1개: .github/oauth-token-trigger — 보안상 제거된 OAuth 교환 워크플로의 잔재입니다. 공개 저장소에 인증 코드 조각이 남아 있던 것으로, 작업 브랜치가 4a39c349에서 이미 지웠습니다. 병합하면 기본 브랜치에서도 사라집니다.

주요 내용

이번 세션에서 고친 것들입니다.

조용한 실패 차단

  • CLAUDE_MODEL 시크릿이 모든 Claude 호출을 400으로 실패시키던 문제
  • 검토 대기 저장소 분리 (목록·본문·보관) + 워크플로 커밋 누락 검사
  • 대시보드 데이터 계약 검사 6종 — 죽은 연결·표시되지 않는 산출물·신선도 누락을 자동으로 잡습니다
  • 차트 라이브러리 로드 실패가 대시보드 전체를 죽이던 문제 (패널별 격리)

연결이 끊겨 있던 곳

  • 시리즈 회차 ↔ 글을 제목 대신 post_id로 연결. final_editor가 제목을 교정하는 순간 끊기던 구조였고, 41편 중 38편이 이미 미아였습니다
  • 저자 경험 자료가 비었는데 1인칭만 허용되던 구멍 차단

대시보드

  • 데이터 신선도 표시 (24개 파일 + 18개 패널 배지), 꺼진 기능은 '중단'이 아니라 '기능 꺼짐'으로 구분
  • 초기 전송 1,855KB → 1,101KB
  • 시스템 탭 26개 섹션을 보는 주기로 3층 정리
  • 기능 스위치를 화면에서 켜고 끄기

운영

  • 실행 이력 보존 12일 → 90일
  • AdSense 필수 페이지 확인 (읽기 전용)

검증

  • 테스트 695건 통과
  • 헤드리스 브라우저로 대시보드 주요 흐름 확인 (초기 로딩, 지연 로딩, 차트 실패, 스위치 토글, 신선도 판정)

병합 방식

merge commit으로 병합해야 합니다. squash하면 kevin-blog-image-studio/가 삭제됩니다.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69


Generated by Claude Code

github-actionsBotand others added 30 commits August 19, 2026 10:47
인수인계 문서 1번·3번 항목입니다.
1) index_priority가 gscProperty/site를 내보내지 않아, 대시보드 색인 패널의
`c.gscProperty || c.site`가 항상 빈 값이었습니다. 다음 색인 점검이 돌면
패널에서 어느 Search Console 속성을 열지 판단할 근거가 없어집니다.
gsc_indexing은 이미 검사에 성공한 속성을 gsc_property로 남기고 있으므로
(URL-프리픽스인지 sc-domain인지가 여기서만 확실해짐) 그 값을 그대로
넘기고, 없으면 site로 대체합니다.
현재 index_status.json에 이 키가 없는 것은 파일이 해당 변경 이전에
생성됐기 때문이며, 다음 점검부터 채워집니다.
2) docs/index (1).html 삭제 — 업로드 중 이름이 바뀌어 생긴 사본입니다.
docs/index.html과 바이트 단위로 동일한 것을 확인하고 지웠습니다.
테스트 3건 추가 (전체 396건 통과)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
인수인계 문서 7번 항목입니다.
docs/data/gsc.json의 sites에 blog2·blog3만 있고 blog1(www.hoguwhat.com)이
빠져 있었습니다. 커스텀 도메인은 Search Console에 도메인 속성(sc-domain:)으로
등록된 경우가 많아 URL-프리픽스 형식으로 조회하면 403이 나는데,
blog_analytics와 gsc_indexing에는 있는 재시도 폴백이 gsc_fetcher에만
없어서 403을 받고 그대로 포기했습니다.
색인 검사(gsc_indexing)에서는 blog1이 정상 조회되고 있어(7건),
문제가 이 모듈에 한정된다는 것도 확인했습니다.
- 403이면 sc-domain 형식으로 한 번 더 조회
- 두 형식 모두 실패할 때만 경고하고, 메시지에 속성 유형 확인을 안내
- 폴백 도입으로 죽은 코드가 된 URL 조립부 제거
- 테스트 7건 추가 (전체 403건 통과)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
6번(기존 발행글 주제 중복)은 조사 결과 실제 중복 콘텐츠가 아니었습니다.
blog1·blog2 발행글의 제목 완전 일치 0건, 키워드 중복 1건뿐이며 겹치는
것은 카테고리일 뿐입니다. 주제 이탈분 71건을 모두 비공개하면 blog2에
9건만 남아 콘텐츠 부족으로 오히려 AdSense 거절 위험이 커지므로,
사용자 판단으로 보류하고 색인 회복을 우선하기로 했습니다.
migrate_offtopic.py가 blog1 전용이고 주제 키워드가 옛 설정 기준으로
하드코딩돼 있다는 점도 함께 남겼습니다 — 그대로 실행하면 오분류합니다.
반증된 가설 표에 "주제 중복이 정책 문제" 항목을 추가했습니다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
워크플로 5개가 기본 브랜치에 반영돼 pending-cleanup(주 1회),
alert-watchdog(6시간마다) 스케줄이 실제로 동작하기 시작합니다.
공개 저장소에서 토큰을 교환하던 get-oauth-token.yml도 제거됐습니다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
실행 #40 결과를 읽다가 "제출 421 / 색인 0 = 0%"로 결론지을 뻔했습니다.
사이트맵 API의 contents[].indexed는 구글이 지원을 중단한 필드로 항상 0이며,
실제 색인 수가 아닙니다. 필드 이름이 indexedUrls면 다음 사람도 같은 오독을
하게 됩니다.
- indexedUrls → indexedUrlsDeprecated 로 이름 변경 (값 자체는 그대로 보존)
- sitemap 결과에 note 추가 — 데이터 옆에 경고가 함께 붙어 다니도록
- sitemap_state docstring에 근거와 대체 판단 방법(index_status.json) 명시
- 모듈 docstring 갱신: 첫 실행 결과 설정 문제가 아님이 확인됐고,
그럼에도 이 진단이 안전망으로 계속 필요한 이유를 남김
- HANDOFF에 제약 항목과 반증된 가설 한 줄 추가, 08-19 진단 결과 반영
(canWrite=true / blog1이 실제 sc-domain 속성 → gsc_fetcher 수정 검증됨)
- 테스트 2건 추가, 옛 이름을 쓰던 기존 테스트 1건 갱신 (전체 405건 통과)
대시보드는 이 숫자를 표시하지 않아 변경하지 않았습니다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
github-actionsBotand others added 26 commits August 23, 2026 15:44
adsense-policy-update 워크플로가 만든 파일을 계약 검사가 잡았습니다.
검증기·작성 프롬프트가 읽는 입력 파일이라 대시보드에 나올 이유가 없습니다.
이 파일이 생겼다는 것 자체가 빈 CLAUDE_MODEL 수정이 실제로 통했다는
증거이기도 합니다 — 그 전에는 이 워크플로의 Claude 호출이 400으로
전부 실패했습니다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
워크플로 커밋 목록에 post_details.json이 빠져 있던 동안 발행된 2편의
FAQ·출처가 버려졌습니다. 목록에는 "FAQ 3개" 배지가 남고 모달을 열면
비어 있는 상태였습니다.
원본은 logs/run_history.json에 남아 있었습니다 — 실행 이력은 별도 경로로
커밋되기 때문입니다. 그걸 대조해 되살렸습니다.
2026-08-23 FAQ 3 / 출처 5 9월 대형마트 할인정보 총정리…
2026-08-22 FAQ 3 / 출처 2 여수 밤바다 오션뷰 숙소…
검증: 전체 100건 중 배지와 내용이 어긋나는 글 0건
recover_post_details.py를 남깁니다. 일회성 스크립트로 때우면 다음에
같은 일이 생겼을 때 다시 처음부터 만들어야 합니다.
- 기본은 미리보기, --apply에서만 기록
- 제목과 키워드가 둘 다 맞아야 같은 글로 봅니다. 제목만 보면 재발행된
동명의 글과 섞입니다
- 원래 FAQ·출처가 없던 글은 대상이 아닙니다 (유실이 아니므로)
- 이력에도 없어 복구 불가한 건수를 따로 보고합니다
누락 자체는 test_workflow_pending_files.py가 막고, 저장소에 빈틈이
남아 있지 않은지는 새 테스트가 계속 확인합니다. 651건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
도구(inbound_linker)는 2026-08-21부터 정상 동작했습니다. 그런데 고아는
165건에서 172건으로 오히려 늘었습니다. 링크가 안 걸린 게 아니라 처리량이
모자랐습니다.
해소: 주 1회 × 10건 = 10건/주
생성: 발행 1건당 고아 1건 → 하루 1편이면 7건/주
순감: 3건/주 → 밀린 172건을 걷어내는 데 57주
내부 링크는 글을 쓸 때 "새 글 → 기존 글" 한 방향으로만 걸립니다. 그래서
모든 새 글은 아무도 가리키지 않는 고아로 태어납니다. 배치를 아무리 빨리
돌려도 발행이 계속되는 한 새 고아는 계속 생깁니다.
두 갈래로 고칩니다.
1. 발행 직후 그 글에 역방향 링크 (blog-run, 블로그 3개 각각)
진단(internal_link_audit)을 먼저 돌려 방금 올린 글을 고아 목록에 넣고,
inbound_linker --limit 5 --apply로 링크를 겁니다. 순서가 중요합니다 —
진단을 건너뛰면 새 글이 목록에 없어 그 글만 계속 고아로 남습니다.
발행을 막을 이유는 없으므로 실패해도 경고만 남깁니다.
산출물(internal_links·inbound_links)을 커밋 목록에 추가했습니다.
빠뜨리면 다음 실행이 낡은 고아 목록을 읽습니다.
2. 밀린 물량은 매일 배치로 (inbound-links, 주간 → 매일, 10 → 25건)
스케줄 실행에는 inputs가 없어 `inputs.limit || 'N'`의 N이 실제 값입니다.
입력란 default만 고치고 이쪽을 놓치면 조용히 옛 처리량으로 돌아가므로
둘을 맞추고 테스트로 고정했습니다.
→ 순감 25건/일, 밀린 172건은 약 7일. 이후 배치는 보조로 남습니다.
기대치에 대해: 같은 측정에서 색인 검사 20건 중 고아 9건과 링크 있는 글
11건이 똑같이 미색인이었습니다. 내부 링크를 만든다고 색인이 풀린다는 근거는
아직 없습니다 — 표본이 전부 미색인이라 인과를 판정할 수 없습니다. 사이트
구조상 정당한 개선으로 보고 진행하며, 색인 해결책으로 광고하지 않습니다.
이 사양이 되돌아가지 않도록 테스트 8건 추가. 659건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
전체 점검에서 author_profile.json이 100% 템플릿(`예: ISA 계좌`)이라
경험형 경로가 한 번도 켜진 적이 없다는 것을 확인했습니다. AdSense가 보는
E-E-A-T 중 Experience 축이 통째로 비어 있는 상태입니다.
① 안전 구멍부터 막았습니다
프롬프트는 경험이 매칭되면 "아래는 글쓴이의 실제 경험 자료입니다"라고
선언하고 1인칭을 허용합니다. 그런데 summary가 비어 있으면 그 선언 아래에
아무것도 못 넣은 채 허가만 나갑니다 — 가짜 체험담을 막으려던 장치가
거꾸로 뚫립니다. summary가 빈 항목은 매칭하지 않습니다.
② author_profile.json 구조를 실제 발행 키워드에 맞춰 정리
템플릿 예시를 걷어내고 4개 분야(금융·여행·골프·생활서비스)의 토픽과
대상 블로그를 채웠습니다. summary·detail은 비워 뒀습니다 — 실제 경험은
지어낼 수 없고, 비어 있는 동안은 매칭되지 않아 글에 영향이 없습니다.
③ author_profile_check.py — 조용한 실패를 눈에 보이게
이 파일은 비어 있어도 워크플로가 초록으로 끝나고 모든 글이 조사형으로
나갑니다. 그게 정상 동작이라 아무도 눈치채지 못합니다. 등록 현황과
"이 항목을 채우면 최근 글 몇 편이 경험형으로 바뀌는지"를 보여 줍니다.
판정은 _match_experience를 그대로 씁니다. 처음에 자체 부분문자열 매칭으로
셌다가 "골프"가 "골프장"과 맞는 것처럼 과대 보고했습니다(17편 → 실제 14편).
실제 매칭은 단어 단위이고, 규칙이 두 곳에 살면 반드시 어긋납니다.
현재 최근 40편 중 0편이 경험형이며, 4개 항목을 채우면 14편이 대상입니다.
민감정보 유입을 막는 검사(전화·주민번호·이메일 패턴)도 함께 넣었습니다.
테스트 12건 추가.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
회차와 글을 잇는 유일한 끈이 제목이었습니다. 그런데 final_editor가 생성 후
제목을 교정합니다(post_data["title"] = new_title). 그 순간 연결이 끊기고,
대시보드는 "매칭되는 포스트를 찾을 수 없습니다"만 띄웁니다 — 고장을 고친 게
아니라 고장 났을 때 띄울 메시지를 만들어 둔 상태였습니다.
점검 결과 pending_review 41편 중 38편이 검토 대기·보관·발행 목록·레지스트리
300건 어디에서도 제목으로 찾히지 않았습니다.
앞으로
- save_pending_posts가 배정한 id를 돌려줍니다. 건너뛴 항목도 빈 문자열로
자리를 지켜 호출부가 순서대로 짝지을 수 있습니다
- 시리즈 발행 흐름이 그 id를 회차에 기록합니다. 짝은 별도 목록으로 들고
다닙니다 — 회차에 임시 참조를 달면 루프 중간의 save_series가 그걸
series.json에 직렬화합니다
- 대시보드가 post_id를 먼저 보고, 없을 때만 제목·키워드로 폴백합니다
이미 끊긴 것
- series_relink.py: 제목 일치 → 앞부분 일치 → 그래도 없으면 status를
'lost'로. 'pending_review'로 두면 열 수 없는 일감이 검토 목록에 계속
남습니다. 왜 lost인지 사유도 함께 적어 나중에 다시 조사하지 않게 합니다
- 반영 결과: 앞부분 일치 3건 연결, 38건 lost
(done 15 / lost 38 / pending_review 3 / upload_failed 9)
탐색 범위를 넓히다 알게 된 것: posts.json은 최근 100편만 담고 있어 7월 회차를
못 봅니다. 레지스트리 300건까지 넣어 다시 확인한 뒤에야 "어디에도 없다"고
결론 냈습니다. 처음 범위로는 성급한 판정이 될 뻔했습니다.
제목 앞부분 매칭은 앞 14자 안에서 갈라지면 실패합니다. 그 한계를 테스트로
고정해 두었고, 그래서 앞으로는 id로 잇습니다.
테스트 9건 추가, 680건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
page_creator.py가 개인정보처리방침·소개 페이지를 만들 수 있는데, 그걸 부르는
진입점은 main.py --setup 하나뿐이고 어떤 워크플로도 그 경로를 실행하지
않습니다. 누군가 로컬에서 한 번 돌렸는지에 달려 있고, 돌렸더라도 나중에
지워졌는지 아무도 모릅니다. 심사에서 바로 걸리는 항목인데 시스템 어디에도
"있다/없다"가 기록되지 않았습니다.
- required_pages_check.py: 블로그별로 세 페이지(개인정보처리방침·소개·문의)
존재 여부만 확인합니다. 만들지 않습니다 — 발행 중인 블로그에 페이지를
얹는 건 사람이 정할 일이라, 없으면 만드는 명령만 알립니다
- 제목 별칭 매칭: 손으로 만들었다면 "개인정보 처리방침"처럼 띄어쓰기가
다를 수 있어 별칭으로도 찾습니다
- adsense-audit에 읽기 전용 단계 추가 + 리포트 커밋
- 대시보드 AdSense 감사 패널 위에 상태 한 줄
가장 신경 쓴 부분은 "모른다"와 "없다"를 섞지 않는 것입니다. 토큰 발급에
실패하면 확인 불가로 기록하고 누락으로 세지 않습니다 — 그러지 않으면
멀쩡한 블로그를 고치라고 하게 됩니다. 반대로 한 블로그도 확인하지 못했으면
ok로 끝내지 않습니다 (사이트맵에서 "한 건도 못 보냈는데 실패 0이라 성공"으로
읽던 것과 같은 유형).
이 환경에서는 자격증명이 없어 실제 페이지 존재 여부를 확인하지 못했습니다.
현재 리포트는 unknown 3 / ok false이며, 워크플로 첫 실행에서 실제 상태가
나옵니다.
헤드리스로 세 가지 표시(확인 불가·누락·정상)를 모두 확인했습니다.
테스트 8건 추가, 688건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
지금까지 스위치는 화면에 상태만 보여 줬고, 바꾸려면 저장소 파일을 직접
고쳐야 했습니다. 이제 버튼으로 바꿉니다.
동작
- GitHub API로 blog-automation/feature_flags.json에 커밋합니다
(쿠팡 링크 저장과 같은 방식 — 대시보드는 정적 페이지라 파일을 직접
고치지 못합니다). 러너가 읽는 그 파일이 원본이고, docs/data 쪽은 표시용
사본입니다
- 저장 전에 최신 내용을 받아 그 위에 한 항목만 바꿉니다. 화면의 사본으로
통째로 덮어쓰면 그 사이 다른 곳에서 바뀐 스위치를 되돌립니다
- PAT가 없으면 버튼을 비활성화하고 어디서 설정하는지 안내합니다
- 표에 없는 새 스위치도 이름 그대로 화면에 나옵니다
social_content 스위치 신설 + 배선
SOCIAL_AUTO_PUBLISH=false여도 콘텐츠는 매번 만들고 게시만 건너뛰었습니다.
실제로 50건 중 39건이 전부 skipped이고 게시는 0건인데, 생성에 드는 Claude
호출과 356KB는 계속 나갔습니다. "게시하지 않는다"와 "만들지도 않는다"는
다른 결정이라 스위치를 따로 뒀고, 확인을 생성 앞에 뒀습니다 — 만들고 나서
버리면 비용은 이미 나간 뒤입니다.
기본값은 꺼짐입니다. 켤 생각이 없는 기능을 계속 돌리고 있었으므로,
대시보드에서 언제든 다시 켤 수 있게 된 지금이 끄기 적절한 시점입니다.
헤드리스 검증
PAT 없음: 버튼 2개 전부 비활성 + 안내 문구
PAT 있음: GET → PUT 순서로 호출, 커밋될 내용에 화면에 없던
other_flag:true가 그대로 보존됨 (덮어쓰기 아님), UI 즉시 갱신
테스트 5건 추가 (스위치를 아무도 안 읽는 장식으로 두지 않도록 main.py가
실제로 확인하는지, 확인이 생성보다 먼저인지, 화면 이름이 빠진 스위치가
없는지 포함). 693건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
소셜 콘텐츠 생성을 끈 직후에 생기는 문제 두 가지를 함께 처리합니다.
기능을 끄는 것만으로는 끝이 아니라, 끈 뒤의 화면도 정직해야 합니다.
① 신선도 패널이 꺼진 기능을 '중단'으로 오인
social.json은 이제 안 늙는 게 정상인데, 기대 주기(24시간)를 넘기면
빨간 '⚠️ 중단'으로 떴을 겁니다. 일부러 끈 기능과 진짜 고장이 같은
색으로 보이면 경고가 의미를 잃습니다.
- SOURCE_META 항목에 그 파일을 만드는 기능 스위치를 적을 수 있게
했습니다: ['소셜 콘텐츠', 24, 'social_content']
- 스위치가 꺼져 있으면 '⏸ 기능 꺼짐'(회색)으로 표시하고 지연 집계에서
제외합니다. 요약에는 "기능 꺼짐 N개"로 따로 셉니다
② 소셜 탭이 옛 기록을 현재처럼 보여 줌
목록에 50건이 그대로 남아 있어, 안내가 없으면 여전히 돌아가는 줄
압니다. 생성이 꺼져 있을 때만 뜨는 안내를 넣었습니다 — 몇 건이
언제까지의 기록인지, 다시 켜려면 어디로 가는지까지.
스위치 이름 오타로 연결이 조용히 끊기면 ①이 원래대로 돌아가므로,
SOURCE_META가 가리키는 스위치가 실제로 있는지 검사합니다. 오타를 넣어
검사가 잡는 것도 확인했습니다.
헤드리스 확인
꺼짐: '⏸ 기능 꺼짐' + "1개 넘김 · 기능 꺼짐 1개" + 소셜 탭 안내 표시
켜짐: 같은 파일이 '⚠️ 30일 전'으로 지연 집계에 포함, 안내 숨김
테스트 2건 추가, 695건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
@Kevinlee7250
Kevinlee7250 marked this pull request as ready for review August 24, 2026 04:26
@Kevinlee7250
Kevinlee7250 merged commit 67cab1a into latestAug 24, 2026
3 checks passed
github-actionsBot added a commit that referenced this pull request Aug 25, 2026
github-actionsBot added a commit that referenced this pull request Aug 26, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@Kevinlee7250@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

블로그 자동화 작업 브랜치를 기본 브랜치에 병합 - #25

Merged
Kevinlee7250 merged 1122 commits into
latestfrom
claude/blog-automation-system-KAbtE
Aug 24, 2026
Merged

블로그 자동화 작업 브랜치를 기본 브랜치에 병합#25
Kevinlee7250 merged 1122 commits into
latestfrom
claude/blog-automation-system-KAbtE

Conversation

@Kevinlee7250

Copy link
Copy Markdown
Owner

지금까지 워크플로 정의만 latest에 동기화하고(#22·#23·#24) 실행 코드는 작업 브랜치에 두었습니다. 러너가 작업 브랜치를 체크아웃해 쓰기 때문에 동작에는 문제가 없었지만, 두 브랜치가 1,122커밋 갈라진 상태입니다. 이번에 코드 전체를 기본 브랜치로 가져옵니다.

병합 전 확인한 것

로컬에서 실제로 머지를 돌려 결과를 확인했습니다.

Automatic merge went well
충돌 없음
추가 318 · 수정 67 · 삭제 1

kevin-blog-image-studio/ 보존됨. 이 디렉터리는 latest에만 있고 작업 브랜치에는 없어서 단순 diff로는 "28개 파일 삭제"로 보입니다. 공통 조상(2026-07-23)에도 없고 latest에서 나중에 추가된 것이라, 일반 머지에서는 그대로 남습니다 — 시뮬레이션으로 확인했습니다. 이 때문에 squash가 아니라 merge commit이 필요합니다.

삭제되는 파일 1개: .github/oauth-token-trigger — 보안상 제거된 OAuth 교환 워크플로의 잔재입니다. 공개 저장소에 인증 코드 조각이 남아 있던 것으로, 작업 브랜치가 4a39c349에서 이미 지웠습니다. 병합하면 기본 브랜치에서도 사라집니다.

주요 내용

이번 세션에서 고친 것들입니다.

조용한 실패 차단

  • CLAUDE_MODEL 시크릿이 모든 Claude 호출을 400으로 실패시키던 문제
  • 검토 대기 저장소 분리 (목록·본문·보관) + 워크플로 커밋 누락 검사
  • 대시보드 데이터 계약 검사 6종 — 죽은 연결·표시되지 않는 산출물·신선도 누락을 자동으로 잡습니다
  • 차트 라이브러리 로드 실패가 대시보드 전체를 죽이던 문제 (패널별 격리)

연결이 끊겨 있던 곳

  • 시리즈 회차 ↔ 글을 제목 대신 post_id로 연결. final_editor가 제목을 교정하는 순간 끊기던 구조였고, 41편 중 38편이 이미 미아였습니다
  • 저자 경험 자료가 비었는데 1인칭만 허용되던 구멍 차단

대시보드

  • 데이터 신선도 표시 (24개 파일 + 18개 패널 배지), 꺼진 기능은 '중단'이 아니라 '기능 꺼짐'으로 구분
  • 초기 전송 1,855KB → 1,101KB
  • 시스템 탭 26개 섹션을 보는 주기로 3층 정리
  • 기능 스위치를 화면에서 켜고 끄기

운영

  • 실행 이력 보존 12일 → 90일
  • AdSense 필수 페이지 확인 (읽기 전용)

검증

  • 테스트 695건 통과
  • 헤드리스 브라우저로 대시보드 주요 흐름 확인 (초기 로딩, 지연 로딩, 차트 실패, 스위치 토글, 신선도 판정)

병합 방식

merge commit으로 병합해야 합니다. squash하면 kevin-blog-image-studio/가 삭제됩니다.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69


Generated by Claude Code

github-actionsBotand others added 30 commits August 19, 2026 10:47
인수인계 문서 1번·3번 항목입니다.
1) index_priority가 gscProperty/site를 내보내지 않아, 대시보드 색인 패널의
`c.gscProperty || c.site`가 항상 빈 값이었습니다. 다음 색인 점검이 돌면
패널에서 어느 Search Console 속성을 열지 판단할 근거가 없어집니다.
gsc_indexing은 이미 검사에 성공한 속성을 gsc_property로 남기고 있으므로
(URL-프리픽스인지 sc-domain인지가 여기서만 확실해짐) 그 값을 그대로
넘기고, 없으면 site로 대체합니다.
현재 index_status.json에 이 키가 없는 것은 파일이 해당 변경 이전에
생성됐기 때문이며, 다음 점검부터 채워집니다.
2) docs/index (1).html 삭제 — 업로드 중 이름이 바뀌어 생긴 사본입니다.
docs/index.html과 바이트 단위로 동일한 것을 확인하고 지웠습니다.
테스트 3건 추가 (전체 396건 통과)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
인수인계 문서 7번 항목입니다.
docs/data/gsc.json의 sites에 blog2·blog3만 있고 blog1(www.hoguwhat.com)이
빠져 있었습니다. 커스텀 도메인은 Search Console에 도메인 속성(sc-domain:)으로
등록된 경우가 많아 URL-프리픽스 형식으로 조회하면 403이 나는데,
blog_analytics와 gsc_indexing에는 있는 재시도 폴백이 gsc_fetcher에만
없어서 403을 받고 그대로 포기했습니다.
색인 검사(gsc_indexing)에서는 blog1이 정상 조회되고 있어(7건),
문제가 이 모듈에 한정된다는 것도 확인했습니다.
- 403이면 sc-domain 형식으로 한 번 더 조회
- 두 형식 모두 실패할 때만 경고하고, 메시지에 속성 유형 확인을 안내
- 폴백 도입으로 죽은 코드가 된 URL 조립부 제거
- 테스트 7건 추가 (전체 403건 통과)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
6번(기존 발행글 주제 중복)은 조사 결과 실제 중복 콘텐츠가 아니었습니다.
blog1·blog2 발행글의 제목 완전 일치 0건, 키워드 중복 1건뿐이며 겹치는
것은 카테고리일 뿐입니다. 주제 이탈분 71건을 모두 비공개하면 blog2에
9건만 남아 콘텐츠 부족으로 오히려 AdSense 거절 위험이 커지므로,
사용자 판단으로 보류하고 색인 회복을 우선하기로 했습니다.
migrate_offtopic.py가 blog1 전용이고 주제 키워드가 옛 설정 기준으로
하드코딩돼 있다는 점도 함께 남겼습니다 — 그대로 실행하면 오분류합니다.
반증된 가설 표에 "주제 중복이 정책 문제" 항목을 추가했습니다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
워크플로 5개가 기본 브랜치에 반영돼 pending-cleanup(주 1회),
alert-watchdog(6시간마다) 스케줄이 실제로 동작하기 시작합니다.
공개 저장소에서 토큰을 교환하던 get-oauth-token.yml도 제거됐습니다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
실행 #40 결과를 읽다가 "제출 421 / 색인 0 = 0%"로 결론지을 뻔했습니다.
사이트맵 API의 contents[].indexed는 구글이 지원을 중단한 필드로 항상 0이며,
실제 색인 수가 아닙니다. 필드 이름이 indexedUrls면 다음 사람도 같은 오독을
하게 됩니다.
- indexedUrls → indexedUrlsDeprecated 로 이름 변경 (값 자체는 그대로 보존)
- sitemap 결과에 note 추가 — 데이터 옆에 경고가 함께 붙어 다니도록
- sitemap_state docstring에 근거와 대체 판단 방법(index_status.json) 명시
- 모듈 docstring 갱신: 첫 실행 결과 설정 문제가 아님이 확인됐고,
그럼에도 이 진단이 안전망으로 계속 필요한 이유를 남김
- HANDOFF에 제약 항목과 반증된 가설 한 줄 추가, 08-19 진단 결과 반영
(canWrite=true / blog1이 실제 sc-domain 속성 → gsc_fetcher 수정 검증됨)
- 테스트 2건 추가, 옛 이름을 쓰던 기존 테스트 1건 갱신 (전체 405건 통과)
대시보드는 이 숫자를 표시하지 않아 변경하지 않았습니다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
github-actionsBotand others added 26 commits August 23, 2026 15:44
adsense-policy-update 워크플로가 만든 파일을 계약 검사가 잡았습니다.
검증기·작성 프롬프트가 읽는 입력 파일이라 대시보드에 나올 이유가 없습니다.
이 파일이 생겼다는 것 자체가 빈 CLAUDE_MODEL 수정이 실제로 통했다는
증거이기도 합니다 — 그 전에는 이 워크플로의 Claude 호출이 400으로
전부 실패했습니다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
워크플로 커밋 목록에 post_details.json이 빠져 있던 동안 발행된 2편의
FAQ·출처가 버려졌습니다. 목록에는 "FAQ 3개" 배지가 남고 모달을 열면
비어 있는 상태였습니다.
원본은 logs/run_history.json에 남아 있었습니다 — 실행 이력은 별도 경로로
커밋되기 때문입니다. 그걸 대조해 되살렸습니다.
2026-08-23 FAQ 3 / 출처 5 9월 대형마트 할인정보 총정리…
2026-08-22 FAQ 3 / 출처 2 여수 밤바다 오션뷰 숙소…
검증: 전체 100건 중 배지와 내용이 어긋나는 글 0건
recover_post_details.py를 남깁니다. 일회성 스크립트로 때우면 다음에
같은 일이 생겼을 때 다시 처음부터 만들어야 합니다.
- 기본은 미리보기, --apply에서만 기록
- 제목과 키워드가 둘 다 맞아야 같은 글로 봅니다. 제목만 보면 재발행된
동명의 글과 섞입니다
- 원래 FAQ·출처가 없던 글은 대상이 아닙니다 (유실이 아니므로)
- 이력에도 없어 복구 불가한 건수를 따로 보고합니다
누락 자체는 test_workflow_pending_files.py가 막고, 저장소에 빈틈이
남아 있지 않은지는 새 테스트가 계속 확인합니다. 651건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
도구(inbound_linker)는 2026-08-21부터 정상 동작했습니다. 그런데 고아는
165건에서 172건으로 오히려 늘었습니다. 링크가 안 걸린 게 아니라 처리량이
모자랐습니다.
해소: 주 1회 × 10건 = 10건/주
생성: 발행 1건당 고아 1건 → 하루 1편이면 7건/주
순감: 3건/주 → 밀린 172건을 걷어내는 데 57주
내부 링크는 글을 쓸 때 "새 글 → 기존 글" 한 방향으로만 걸립니다. 그래서
모든 새 글은 아무도 가리키지 않는 고아로 태어납니다. 배치를 아무리 빨리
돌려도 발행이 계속되는 한 새 고아는 계속 생깁니다.
두 갈래로 고칩니다.
1. 발행 직후 그 글에 역방향 링크 (blog-run, 블로그 3개 각각)
진단(internal_link_audit)을 먼저 돌려 방금 올린 글을 고아 목록에 넣고,
inbound_linker --limit 5 --apply로 링크를 겁니다. 순서가 중요합니다 —
진단을 건너뛰면 새 글이 목록에 없어 그 글만 계속 고아로 남습니다.
발행을 막을 이유는 없으므로 실패해도 경고만 남깁니다.
산출물(internal_links·inbound_links)을 커밋 목록에 추가했습니다.
빠뜨리면 다음 실행이 낡은 고아 목록을 읽습니다.
2. 밀린 물량은 매일 배치로 (inbound-links, 주간 → 매일, 10 → 25건)
스케줄 실행에는 inputs가 없어 `inputs.limit || 'N'`의 N이 실제 값입니다.
입력란 default만 고치고 이쪽을 놓치면 조용히 옛 처리량으로 돌아가므로
둘을 맞추고 테스트로 고정했습니다.
→ 순감 25건/일, 밀린 172건은 약 7일. 이후 배치는 보조로 남습니다.
기대치에 대해: 같은 측정에서 색인 검사 20건 중 고아 9건과 링크 있는 글
11건이 똑같이 미색인이었습니다. 내부 링크를 만든다고 색인이 풀린다는 근거는
아직 없습니다 — 표본이 전부 미색인이라 인과를 판정할 수 없습니다. 사이트
구조상 정당한 개선으로 보고 진행하며, 색인 해결책으로 광고하지 않습니다.
이 사양이 되돌아가지 않도록 테스트 8건 추가. 659건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
전체 점검에서 author_profile.json이 100% 템플릿(`예: ISA 계좌`)이라
경험형 경로가 한 번도 켜진 적이 없다는 것을 확인했습니다. AdSense가 보는
E-E-A-T 중 Experience 축이 통째로 비어 있는 상태입니다.
① 안전 구멍부터 막았습니다
프롬프트는 경험이 매칭되면 "아래는 글쓴이의 실제 경험 자료입니다"라고
선언하고 1인칭을 허용합니다. 그런데 summary가 비어 있으면 그 선언 아래에
아무것도 못 넣은 채 허가만 나갑니다 — 가짜 체험담을 막으려던 장치가
거꾸로 뚫립니다. summary가 빈 항목은 매칭하지 않습니다.
② author_profile.json 구조를 실제 발행 키워드에 맞춰 정리
템플릿 예시를 걷어내고 4개 분야(금융·여행·골프·생활서비스)의 토픽과
대상 블로그를 채웠습니다. summary·detail은 비워 뒀습니다 — 실제 경험은
지어낼 수 없고, 비어 있는 동안은 매칭되지 않아 글에 영향이 없습니다.
③ author_profile_check.py — 조용한 실패를 눈에 보이게
이 파일은 비어 있어도 워크플로가 초록으로 끝나고 모든 글이 조사형으로
나갑니다. 그게 정상 동작이라 아무도 눈치채지 못합니다. 등록 현황과
"이 항목을 채우면 최근 글 몇 편이 경험형으로 바뀌는지"를 보여 줍니다.
판정은 _match_experience를 그대로 씁니다. 처음에 자체 부분문자열 매칭으로
셌다가 "골프"가 "골프장"과 맞는 것처럼 과대 보고했습니다(17편 → 실제 14편).
실제 매칭은 단어 단위이고, 규칙이 두 곳에 살면 반드시 어긋납니다.
현재 최근 40편 중 0편이 경험형이며, 4개 항목을 채우면 14편이 대상입니다.
민감정보 유입을 막는 검사(전화·주민번호·이메일 패턴)도 함께 넣었습니다.
테스트 12건 추가.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
회차와 글을 잇는 유일한 끈이 제목이었습니다. 그런데 final_editor가 생성 후
제목을 교정합니다(post_data["title"] = new_title). 그 순간 연결이 끊기고,
대시보드는 "매칭되는 포스트를 찾을 수 없습니다"만 띄웁니다 — 고장을 고친 게
아니라 고장 났을 때 띄울 메시지를 만들어 둔 상태였습니다.
점검 결과 pending_review 41편 중 38편이 검토 대기·보관·발행 목록·레지스트리
300건 어디에서도 제목으로 찾히지 않았습니다.
앞으로
- save_pending_posts가 배정한 id를 돌려줍니다. 건너뛴 항목도 빈 문자열로
자리를 지켜 호출부가 순서대로 짝지을 수 있습니다
- 시리즈 발행 흐름이 그 id를 회차에 기록합니다. 짝은 별도 목록으로 들고
다닙니다 — 회차에 임시 참조를 달면 루프 중간의 save_series가 그걸
series.json에 직렬화합니다
- 대시보드가 post_id를 먼저 보고, 없을 때만 제목·키워드로 폴백합니다
이미 끊긴 것
- series_relink.py: 제목 일치 → 앞부분 일치 → 그래도 없으면 status를
'lost'로. 'pending_review'로 두면 열 수 없는 일감이 검토 목록에 계속
남습니다. 왜 lost인지 사유도 함께 적어 나중에 다시 조사하지 않게 합니다
- 반영 결과: 앞부분 일치 3건 연결, 38건 lost
(done 15 / lost 38 / pending_review 3 / upload_failed 9)
탐색 범위를 넓히다 알게 된 것: posts.json은 최근 100편만 담고 있어 7월 회차를
못 봅니다. 레지스트리 300건까지 넣어 다시 확인한 뒤에야 "어디에도 없다"고
결론 냈습니다. 처음 범위로는 성급한 판정이 될 뻔했습니다.
제목 앞부분 매칭은 앞 14자 안에서 갈라지면 실패합니다. 그 한계를 테스트로
고정해 두었고, 그래서 앞으로는 id로 잇습니다.
테스트 9건 추가, 680건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
page_creator.py가 개인정보처리방침·소개 페이지를 만들 수 있는데, 그걸 부르는
진입점은 main.py --setup 하나뿐이고 어떤 워크플로도 그 경로를 실행하지
않습니다. 누군가 로컬에서 한 번 돌렸는지에 달려 있고, 돌렸더라도 나중에
지워졌는지 아무도 모릅니다. 심사에서 바로 걸리는 항목인데 시스템 어디에도
"있다/없다"가 기록되지 않았습니다.
- required_pages_check.py: 블로그별로 세 페이지(개인정보처리방침·소개·문의)
존재 여부만 확인합니다. 만들지 않습니다 — 발행 중인 블로그에 페이지를
얹는 건 사람이 정할 일이라, 없으면 만드는 명령만 알립니다
- 제목 별칭 매칭: 손으로 만들었다면 "개인정보 처리방침"처럼 띄어쓰기가
다를 수 있어 별칭으로도 찾습니다
- adsense-audit에 읽기 전용 단계 추가 + 리포트 커밋
- 대시보드 AdSense 감사 패널 위에 상태 한 줄
가장 신경 쓴 부분은 "모른다"와 "없다"를 섞지 않는 것입니다. 토큰 발급에
실패하면 확인 불가로 기록하고 누락으로 세지 않습니다 — 그러지 않으면
멀쩡한 블로그를 고치라고 하게 됩니다. 반대로 한 블로그도 확인하지 못했으면
ok로 끝내지 않습니다 (사이트맵에서 "한 건도 못 보냈는데 실패 0이라 성공"으로
읽던 것과 같은 유형).
이 환경에서는 자격증명이 없어 실제 페이지 존재 여부를 확인하지 못했습니다.
현재 리포트는 unknown 3 / ok false이며, 워크플로 첫 실행에서 실제 상태가
나옵니다.
헤드리스로 세 가지 표시(확인 불가·누락·정상)를 모두 확인했습니다.
테스트 8건 추가, 688건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
지금까지 스위치는 화면에 상태만 보여 줬고, 바꾸려면 저장소 파일을 직접
고쳐야 했습니다. 이제 버튼으로 바꿉니다.
동작
- GitHub API로 blog-automation/feature_flags.json에 커밋합니다
(쿠팡 링크 저장과 같은 방식 — 대시보드는 정적 페이지라 파일을 직접
고치지 못합니다). 러너가 읽는 그 파일이 원본이고, docs/data 쪽은 표시용
사본입니다
- 저장 전에 최신 내용을 받아 그 위에 한 항목만 바꿉니다. 화면의 사본으로
통째로 덮어쓰면 그 사이 다른 곳에서 바뀐 스위치를 되돌립니다
- PAT가 없으면 버튼을 비활성화하고 어디서 설정하는지 안내합니다
- 표에 없는 새 스위치도 이름 그대로 화면에 나옵니다
social_content 스위치 신설 + 배선
SOCIAL_AUTO_PUBLISH=false여도 콘텐츠는 매번 만들고 게시만 건너뛰었습니다.
실제로 50건 중 39건이 전부 skipped이고 게시는 0건인데, 생성에 드는 Claude
호출과 356KB는 계속 나갔습니다. "게시하지 않는다"와 "만들지도 않는다"는
다른 결정이라 스위치를 따로 뒀고, 확인을 생성 앞에 뒀습니다 — 만들고 나서
버리면 비용은 이미 나간 뒤입니다.
기본값은 꺼짐입니다. 켤 생각이 없는 기능을 계속 돌리고 있었으므로,
대시보드에서 언제든 다시 켤 수 있게 된 지금이 끄기 적절한 시점입니다.
헤드리스 검증
PAT 없음: 버튼 2개 전부 비활성 + 안내 문구
PAT 있음: GET → PUT 순서로 호출, 커밋될 내용에 화면에 없던
other_flag:true가 그대로 보존됨 (덮어쓰기 아님), UI 즉시 갱신
테스트 5건 추가 (스위치를 아무도 안 읽는 장식으로 두지 않도록 main.py가
실제로 확인하는지, 확인이 생성보다 먼저인지, 화면 이름이 빠진 스위치가
없는지 포함). 693건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
소셜 콘텐츠 생성을 끈 직후에 생기는 문제 두 가지를 함께 처리합니다.
기능을 끄는 것만으로는 끝이 아니라, 끈 뒤의 화면도 정직해야 합니다.
① 신선도 패널이 꺼진 기능을 '중단'으로 오인
social.json은 이제 안 늙는 게 정상인데, 기대 주기(24시간)를 넘기면
빨간 '⚠️ 중단'으로 떴을 겁니다. 일부러 끈 기능과 진짜 고장이 같은
색으로 보이면 경고가 의미를 잃습니다.
- SOURCE_META 항목에 그 파일을 만드는 기능 스위치를 적을 수 있게
했습니다: ['소셜 콘텐츠', 24, 'social_content']
- 스위치가 꺼져 있으면 '⏸ 기능 꺼짐'(회색)으로 표시하고 지연 집계에서
제외합니다. 요약에는 "기능 꺼짐 N개"로 따로 셉니다
② 소셜 탭이 옛 기록을 현재처럼 보여 줌
목록에 50건이 그대로 남아 있어, 안내가 없으면 여전히 돌아가는 줄
압니다. 생성이 꺼져 있을 때만 뜨는 안내를 넣었습니다 — 몇 건이
언제까지의 기록인지, 다시 켜려면 어디로 가는지까지.
스위치 이름 오타로 연결이 조용히 끊기면 ①이 원래대로 돌아가므로,
SOURCE_META가 가리키는 스위치가 실제로 있는지 검사합니다. 오타를 넣어
검사가 잡는 것도 확인했습니다.
헤드리스 확인
꺼짐: '⏸ 기능 꺼짐' + "1개 넘김 · 기능 꺼짐 1개" + 소셜 탭 안내 표시
켜짐: 같은 파일이 '⚠️ 30일 전'으로 지연 집계에 포함, 안내 숨김
테스트 2건 추가, 695건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
@Kevinlee7250
Kevinlee7250 marked this pull request as ready for review August 24, 2026 04:26
@Kevinlee7250
Kevinlee7250 merged commit 67cab1a into latestAug 24, 2026
3 checks passed
github-actionsBot added a commit that referenced this pull request Aug 25, 2026
github-actionsBot added a commit that referenced this pull request Aug 26, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@Kevinlee7250@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

블로그 자동화 작업 브랜치를 기본 브랜치에 병합 - #25

Merged
Kevinlee7250 merged 1122 commits into
latestfrom
claude/blog-automation-system-KAbtE
Aug 24, 2026
Merged

블로그 자동화 작업 브랜치를 기본 브랜치에 병합#25
Kevinlee7250 merged 1122 commits into
latestfrom
claude/blog-automation-system-KAbtE

Conversation

@Kevinlee7250

Copy link
Copy Markdown
Owner

지금까지 워크플로 정의만 latest에 동기화하고(#22·#23·#24) 실행 코드는 작업 브랜치에 두었습니다. 러너가 작업 브랜치를 체크아웃해 쓰기 때문에 동작에는 문제가 없었지만, 두 브랜치가 1,122커밋 갈라진 상태입니다. 이번에 코드 전체를 기본 브랜치로 가져옵니다.

병합 전 확인한 것

로컬에서 실제로 머지를 돌려 결과를 확인했습니다.

Automatic merge went well
충돌 없음
추가 318 · 수정 67 · 삭제 1

kevin-blog-image-studio/ 보존됨. 이 디렉터리는 latest에만 있고 작업 브랜치에는 없어서 단순 diff로는 "28개 파일 삭제"로 보입니다. 공통 조상(2026-07-23)에도 없고 latest에서 나중에 추가된 것이라, 일반 머지에서는 그대로 남습니다 — 시뮬레이션으로 확인했습니다. 이 때문에 squash가 아니라 merge commit이 필요합니다.

삭제되는 파일 1개: .github/oauth-token-trigger — 보안상 제거된 OAuth 교환 워크플로의 잔재입니다. 공개 저장소에 인증 코드 조각이 남아 있던 것으로, 작업 브랜치가 4a39c349에서 이미 지웠습니다. 병합하면 기본 브랜치에서도 사라집니다.

주요 내용

이번 세션에서 고친 것들입니다.

조용한 실패 차단

  • CLAUDE_MODEL 시크릿이 모든 Claude 호출을 400으로 실패시키던 문제
  • 검토 대기 저장소 분리 (목록·본문·보관) + 워크플로 커밋 누락 검사
  • 대시보드 데이터 계약 검사 6종 — 죽은 연결·표시되지 않는 산출물·신선도 누락을 자동으로 잡습니다
  • 차트 라이브러리 로드 실패가 대시보드 전체를 죽이던 문제 (패널별 격리)

연결이 끊겨 있던 곳

  • 시리즈 회차 ↔ 글을 제목 대신 post_id로 연결. final_editor가 제목을 교정하는 순간 끊기던 구조였고, 41편 중 38편이 이미 미아였습니다
  • 저자 경험 자료가 비었는데 1인칭만 허용되던 구멍 차단

대시보드

  • 데이터 신선도 표시 (24개 파일 + 18개 패널 배지), 꺼진 기능은 '중단'이 아니라 '기능 꺼짐'으로 구분
  • 초기 전송 1,855KB → 1,101KB
  • 시스템 탭 26개 섹션을 보는 주기로 3층 정리
  • 기능 스위치를 화면에서 켜고 끄기

운영

  • 실행 이력 보존 12일 → 90일
  • AdSense 필수 페이지 확인 (읽기 전용)

검증

  • 테스트 695건 통과
  • 헤드리스 브라우저로 대시보드 주요 흐름 확인 (초기 로딩, 지연 로딩, 차트 실패, 스위치 토글, 신선도 판정)

병합 방식

merge commit으로 병합해야 합니다. squash하면 kevin-blog-image-studio/가 삭제됩니다.

🤖 Generated with Claude Code

https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69


Generated by Claude Code

github-actionsBotand others added 30 commits August 19, 2026 10:47
인수인계 문서 1번·3번 항목입니다.
1) index_priority가 gscProperty/site를 내보내지 않아, 대시보드 색인 패널의
`c.gscProperty || c.site`가 항상 빈 값이었습니다. 다음 색인 점검이 돌면
패널에서 어느 Search Console 속성을 열지 판단할 근거가 없어집니다.
gsc_indexing은 이미 검사에 성공한 속성을 gsc_property로 남기고 있으므로
(URL-프리픽스인지 sc-domain인지가 여기서만 확실해짐) 그 값을 그대로
넘기고, 없으면 site로 대체합니다.
현재 index_status.json에 이 키가 없는 것은 파일이 해당 변경 이전에
생성됐기 때문이며, 다음 점검부터 채워집니다.
2) docs/index (1).html 삭제 — 업로드 중 이름이 바뀌어 생긴 사본입니다.
docs/index.html과 바이트 단위로 동일한 것을 확인하고 지웠습니다.
테스트 3건 추가 (전체 396건 통과)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
인수인계 문서 7번 항목입니다.
docs/data/gsc.json의 sites에 blog2·blog3만 있고 blog1(www.hoguwhat.com)이
빠져 있었습니다. 커스텀 도메인은 Search Console에 도메인 속성(sc-domain:)으로
등록된 경우가 많아 URL-프리픽스 형식으로 조회하면 403이 나는데,
blog_analytics와 gsc_indexing에는 있는 재시도 폴백이 gsc_fetcher에만
없어서 403을 받고 그대로 포기했습니다.
색인 검사(gsc_indexing)에서는 blog1이 정상 조회되고 있어(7건),
문제가 이 모듈에 한정된다는 것도 확인했습니다.
- 403이면 sc-domain 형식으로 한 번 더 조회
- 두 형식 모두 실패할 때만 경고하고, 메시지에 속성 유형 확인을 안내
- 폴백 도입으로 죽은 코드가 된 URL 조립부 제거
- 테스트 7건 추가 (전체 403건 통과)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
6번(기존 발행글 주제 중복)은 조사 결과 실제 중복 콘텐츠가 아니었습니다.
blog1·blog2 발행글의 제목 완전 일치 0건, 키워드 중복 1건뿐이며 겹치는
것은 카테고리일 뿐입니다. 주제 이탈분 71건을 모두 비공개하면 blog2에
9건만 남아 콘텐츠 부족으로 오히려 AdSense 거절 위험이 커지므로,
사용자 판단으로 보류하고 색인 회복을 우선하기로 했습니다.
migrate_offtopic.py가 blog1 전용이고 주제 키워드가 옛 설정 기준으로
하드코딩돼 있다는 점도 함께 남겼습니다 — 그대로 실행하면 오분류합니다.
반증된 가설 표에 "주제 중복이 정책 문제" 항목을 추가했습니다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
워크플로 5개가 기본 브랜치에 반영돼 pending-cleanup(주 1회),
alert-watchdog(6시간마다) 스케줄이 실제로 동작하기 시작합니다.
공개 저장소에서 토큰을 교환하던 get-oauth-token.yml도 제거됐습니다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
실행 #40 결과를 읽다가 "제출 421 / 색인 0 = 0%"로 결론지을 뻔했습니다.
사이트맵 API의 contents[].indexed는 구글이 지원을 중단한 필드로 항상 0이며,
실제 색인 수가 아닙니다. 필드 이름이 indexedUrls면 다음 사람도 같은 오독을
하게 됩니다.
- indexedUrls → indexedUrlsDeprecated 로 이름 변경 (값 자체는 그대로 보존)
- sitemap 결과에 note 추가 — 데이터 옆에 경고가 함께 붙어 다니도록
- sitemap_state docstring에 근거와 대체 판단 방법(index_status.json) 명시
- 모듈 docstring 갱신: 첫 실행 결과 설정 문제가 아님이 확인됐고,
그럼에도 이 진단이 안전망으로 계속 필요한 이유를 남김
- HANDOFF에 제약 항목과 반증된 가설 한 줄 추가, 08-19 진단 결과 반영
(canWrite=true / blog1이 실제 sc-domain 속성 → gsc_fetcher 수정 검증됨)
- 테스트 2건 추가, 옛 이름을 쓰던 기존 테스트 1건 갱신 (전체 405건 통과)
대시보드는 이 숫자를 표시하지 않아 변경하지 않았습니다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
github-actionsBotand others added 26 commits August 23, 2026 15:44
adsense-policy-update 워크플로가 만든 파일을 계약 검사가 잡았습니다.
검증기·작성 프롬프트가 읽는 입력 파일이라 대시보드에 나올 이유가 없습니다.
이 파일이 생겼다는 것 자체가 빈 CLAUDE_MODEL 수정이 실제로 통했다는
증거이기도 합니다 — 그 전에는 이 워크플로의 Claude 호출이 400으로
전부 실패했습니다.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
워크플로 커밋 목록에 post_details.json이 빠져 있던 동안 발행된 2편의
FAQ·출처가 버려졌습니다. 목록에는 "FAQ 3개" 배지가 남고 모달을 열면
비어 있는 상태였습니다.
원본은 logs/run_history.json에 남아 있었습니다 — 실행 이력은 별도 경로로
커밋되기 때문입니다. 그걸 대조해 되살렸습니다.
2026-08-23 FAQ 3 / 출처 5 9월 대형마트 할인정보 총정리…
2026-08-22 FAQ 3 / 출처 2 여수 밤바다 오션뷰 숙소…
검증: 전체 100건 중 배지와 내용이 어긋나는 글 0건
recover_post_details.py를 남깁니다. 일회성 스크립트로 때우면 다음에
같은 일이 생겼을 때 다시 처음부터 만들어야 합니다.
- 기본은 미리보기, --apply에서만 기록
- 제목과 키워드가 둘 다 맞아야 같은 글로 봅니다. 제목만 보면 재발행된
동명의 글과 섞입니다
- 원래 FAQ·출처가 없던 글은 대상이 아닙니다 (유실이 아니므로)
- 이력에도 없어 복구 불가한 건수를 따로 보고합니다
누락 자체는 test_workflow_pending_files.py가 막고, 저장소에 빈틈이
남아 있지 않은지는 새 테스트가 계속 확인합니다. 651건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
도구(inbound_linker)는 2026-08-21부터 정상 동작했습니다. 그런데 고아는
165건에서 172건으로 오히려 늘었습니다. 링크가 안 걸린 게 아니라 처리량이
모자랐습니다.
해소: 주 1회 × 10건 = 10건/주
생성: 발행 1건당 고아 1건 → 하루 1편이면 7건/주
순감: 3건/주 → 밀린 172건을 걷어내는 데 57주
내부 링크는 글을 쓸 때 "새 글 → 기존 글" 한 방향으로만 걸립니다. 그래서
모든 새 글은 아무도 가리키지 않는 고아로 태어납니다. 배치를 아무리 빨리
돌려도 발행이 계속되는 한 새 고아는 계속 생깁니다.
두 갈래로 고칩니다.
1. 발행 직후 그 글에 역방향 링크 (blog-run, 블로그 3개 각각)
진단(internal_link_audit)을 먼저 돌려 방금 올린 글을 고아 목록에 넣고,
inbound_linker --limit 5 --apply로 링크를 겁니다. 순서가 중요합니다 —
진단을 건너뛰면 새 글이 목록에 없어 그 글만 계속 고아로 남습니다.
발행을 막을 이유는 없으므로 실패해도 경고만 남깁니다.
산출물(internal_links·inbound_links)을 커밋 목록에 추가했습니다.
빠뜨리면 다음 실행이 낡은 고아 목록을 읽습니다.
2. 밀린 물량은 매일 배치로 (inbound-links, 주간 → 매일, 10 → 25건)
스케줄 실행에는 inputs가 없어 `inputs.limit || 'N'`의 N이 실제 값입니다.
입력란 default만 고치고 이쪽을 놓치면 조용히 옛 처리량으로 돌아가므로
둘을 맞추고 테스트로 고정했습니다.
→ 순감 25건/일, 밀린 172건은 약 7일. 이후 배치는 보조로 남습니다.
기대치에 대해: 같은 측정에서 색인 검사 20건 중 고아 9건과 링크 있는 글
11건이 똑같이 미색인이었습니다. 내부 링크를 만든다고 색인이 풀린다는 근거는
아직 없습니다 — 표본이 전부 미색인이라 인과를 판정할 수 없습니다. 사이트
구조상 정당한 개선으로 보고 진행하며, 색인 해결책으로 광고하지 않습니다.
이 사양이 되돌아가지 않도록 테스트 8건 추가. 659건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
전체 점검에서 author_profile.json이 100% 템플릿(`예: ISA 계좌`)이라
경험형 경로가 한 번도 켜진 적이 없다는 것을 확인했습니다. AdSense가 보는
E-E-A-T 중 Experience 축이 통째로 비어 있는 상태입니다.
① 안전 구멍부터 막았습니다
프롬프트는 경험이 매칭되면 "아래는 글쓴이의 실제 경험 자료입니다"라고
선언하고 1인칭을 허용합니다. 그런데 summary가 비어 있으면 그 선언 아래에
아무것도 못 넣은 채 허가만 나갑니다 — 가짜 체험담을 막으려던 장치가
거꾸로 뚫립니다. summary가 빈 항목은 매칭하지 않습니다.
② author_profile.json 구조를 실제 발행 키워드에 맞춰 정리
템플릿 예시를 걷어내고 4개 분야(금융·여행·골프·생활서비스)의 토픽과
대상 블로그를 채웠습니다. summary·detail은 비워 뒀습니다 — 실제 경험은
지어낼 수 없고, 비어 있는 동안은 매칭되지 않아 글에 영향이 없습니다.
③ author_profile_check.py — 조용한 실패를 눈에 보이게
이 파일은 비어 있어도 워크플로가 초록으로 끝나고 모든 글이 조사형으로
나갑니다. 그게 정상 동작이라 아무도 눈치채지 못합니다. 등록 현황과
"이 항목을 채우면 최근 글 몇 편이 경험형으로 바뀌는지"를 보여 줍니다.
판정은 _match_experience를 그대로 씁니다. 처음에 자체 부분문자열 매칭으로
셌다가 "골프"가 "골프장"과 맞는 것처럼 과대 보고했습니다(17편 → 실제 14편).
실제 매칭은 단어 단위이고, 규칙이 두 곳에 살면 반드시 어긋납니다.
현재 최근 40편 중 0편이 경험형이며, 4개 항목을 채우면 14편이 대상입니다.
민감정보 유입을 막는 검사(전화·주민번호·이메일 패턴)도 함께 넣었습니다.
테스트 12건 추가.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
회차와 글을 잇는 유일한 끈이 제목이었습니다. 그런데 final_editor가 생성 후
제목을 교정합니다(post_data["title"] = new_title). 그 순간 연결이 끊기고,
대시보드는 "매칭되는 포스트를 찾을 수 없습니다"만 띄웁니다 — 고장을 고친 게
아니라 고장 났을 때 띄울 메시지를 만들어 둔 상태였습니다.
점검 결과 pending_review 41편 중 38편이 검토 대기·보관·발행 목록·레지스트리
300건 어디에서도 제목으로 찾히지 않았습니다.
앞으로
- save_pending_posts가 배정한 id를 돌려줍니다. 건너뛴 항목도 빈 문자열로
자리를 지켜 호출부가 순서대로 짝지을 수 있습니다
- 시리즈 발행 흐름이 그 id를 회차에 기록합니다. 짝은 별도 목록으로 들고
다닙니다 — 회차에 임시 참조를 달면 루프 중간의 save_series가 그걸
series.json에 직렬화합니다
- 대시보드가 post_id를 먼저 보고, 없을 때만 제목·키워드로 폴백합니다
이미 끊긴 것
- series_relink.py: 제목 일치 → 앞부분 일치 → 그래도 없으면 status를
'lost'로. 'pending_review'로 두면 열 수 없는 일감이 검토 목록에 계속
남습니다. 왜 lost인지 사유도 함께 적어 나중에 다시 조사하지 않게 합니다
- 반영 결과: 앞부분 일치 3건 연결, 38건 lost
(done 15 / lost 38 / pending_review 3 / upload_failed 9)
탐색 범위를 넓히다 알게 된 것: posts.json은 최근 100편만 담고 있어 7월 회차를
못 봅니다. 레지스트리 300건까지 넣어 다시 확인한 뒤에야 "어디에도 없다"고
결론 냈습니다. 처음 범위로는 성급한 판정이 될 뻔했습니다.
제목 앞부분 매칭은 앞 14자 안에서 갈라지면 실패합니다. 그 한계를 테스트로
고정해 두었고, 그래서 앞으로는 id로 잇습니다.
테스트 9건 추가, 680건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
page_creator.py가 개인정보처리방침·소개 페이지를 만들 수 있는데, 그걸 부르는
진입점은 main.py --setup 하나뿐이고 어떤 워크플로도 그 경로를 실행하지
않습니다. 누군가 로컬에서 한 번 돌렸는지에 달려 있고, 돌렸더라도 나중에
지워졌는지 아무도 모릅니다. 심사에서 바로 걸리는 항목인데 시스템 어디에도
"있다/없다"가 기록되지 않았습니다.
- required_pages_check.py: 블로그별로 세 페이지(개인정보처리방침·소개·문의)
존재 여부만 확인합니다. 만들지 않습니다 — 발행 중인 블로그에 페이지를
얹는 건 사람이 정할 일이라, 없으면 만드는 명령만 알립니다
- 제목 별칭 매칭: 손으로 만들었다면 "개인정보 처리방침"처럼 띄어쓰기가
다를 수 있어 별칭으로도 찾습니다
- adsense-audit에 읽기 전용 단계 추가 + 리포트 커밋
- 대시보드 AdSense 감사 패널 위에 상태 한 줄
가장 신경 쓴 부분은 "모른다"와 "없다"를 섞지 않는 것입니다. 토큰 발급에
실패하면 확인 불가로 기록하고 누락으로 세지 않습니다 — 그러지 않으면
멀쩡한 블로그를 고치라고 하게 됩니다. 반대로 한 블로그도 확인하지 못했으면
ok로 끝내지 않습니다 (사이트맵에서 "한 건도 못 보냈는데 실패 0이라 성공"으로
읽던 것과 같은 유형).
이 환경에서는 자격증명이 없어 실제 페이지 존재 여부를 확인하지 못했습니다.
현재 리포트는 unknown 3 / ok false이며, 워크플로 첫 실행에서 실제 상태가
나옵니다.
헤드리스로 세 가지 표시(확인 불가·누락·정상)를 모두 확인했습니다.
테스트 8건 추가, 688건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
지금까지 스위치는 화면에 상태만 보여 줬고, 바꾸려면 저장소 파일을 직접
고쳐야 했습니다. 이제 버튼으로 바꿉니다.
동작
- GitHub API로 blog-automation/feature_flags.json에 커밋합니다
(쿠팡 링크 저장과 같은 방식 — 대시보드는 정적 페이지라 파일을 직접
고치지 못합니다). 러너가 읽는 그 파일이 원본이고, docs/data 쪽은 표시용
사본입니다
- 저장 전에 최신 내용을 받아 그 위에 한 항목만 바꿉니다. 화면의 사본으로
통째로 덮어쓰면 그 사이 다른 곳에서 바뀐 스위치를 되돌립니다
- PAT가 없으면 버튼을 비활성화하고 어디서 설정하는지 안내합니다
- 표에 없는 새 스위치도 이름 그대로 화면에 나옵니다
social_content 스위치 신설 + 배선
SOCIAL_AUTO_PUBLISH=false여도 콘텐츠는 매번 만들고 게시만 건너뛰었습니다.
실제로 50건 중 39건이 전부 skipped이고 게시는 0건인데, 생성에 드는 Claude
호출과 356KB는 계속 나갔습니다. "게시하지 않는다"와 "만들지도 않는다"는
다른 결정이라 스위치를 따로 뒀고, 확인을 생성 앞에 뒀습니다 — 만들고 나서
버리면 비용은 이미 나간 뒤입니다.
기본값은 꺼짐입니다. 켤 생각이 없는 기능을 계속 돌리고 있었으므로,
대시보드에서 언제든 다시 켤 수 있게 된 지금이 끄기 적절한 시점입니다.
헤드리스 검증
PAT 없음: 버튼 2개 전부 비활성 + 안내 문구
PAT 있음: GET → PUT 순서로 호출, 커밋될 내용에 화면에 없던
other_flag:true가 그대로 보존됨 (덮어쓰기 아님), UI 즉시 갱신
테스트 5건 추가 (스위치를 아무도 안 읽는 장식으로 두지 않도록 main.py가
실제로 확인하는지, 확인이 생성보다 먼저인지, 화면 이름이 빠진 스위치가
없는지 포함). 693건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
소셜 콘텐츠 생성을 끈 직후에 생기는 문제 두 가지를 함께 처리합니다.
기능을 끄는 것만으로는 끝이 아니라, 끈 뒤의 화면도 정직해야 합니다.
① 신선도 패널이 꺼진 기능을 '중단'으로 오인
social.json은 이제 안 늙는 게 정상인데, 기대 주기(24시간)를 넘기면
빨간 '⚠️ 중단'으로 떴을 겁니다. 일부러 끈 기능과 진짜 고장이 같은
색으로 보이면 경고가 의미를 잃습니다.
- SOURCE_META 항목에 그 파일을 만드는 기능 스위치를 적을 수 있게
했습니다: ['소셜 콘텐츠', 24, 'social_content']
- 스위치가 꺼져 있으면 '⏸ 기능 꺼짐'(회색)으로 표시하고 지연 집계에서
제외합니다. 요약에는 "기능 꺼짐 N개"로 따로 셉니다
② 소셜 탭이 옛 기록을 현재처럼 보여 줌
목록에 50건이 그대로 남아 있어, 안내가 없으면 여전히 돌아가는 줄
압니다. 생성이 꺼져 있을 때만 뜨는 안내를 넣었습니다 — 몇 건이
언제까지의 기록인지, 다시 켜려면 어디로 가는지까지.
스위치 이름 오타로 연결이 조용히 끊기면 ①이 원래대로 돌아가므로,
SOURCE_META가 가리키는 스위치가 실제로 있는지 검사합니다. 오타를 넣어
검사가 잡는 것도 확인했습니다.
헤드리스 확인
꺼짐: '⏸ 기능 꺼짐' + "1개 넘김 · 기능 꺼짐 1개" + 소셜 탭 안내 표시
켜짐: 같은 파일이 '⚠️ 30일 전'으로 지연 집계에 포함, 안내 숨김
테스트 2건 추가, 695건 통과.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RgLYqM9puPW1aqHTccbX69
@Kevinlee7250
Kevinlee7250 marked this pull request as ready for review August 24, 2026 04:26
@Kevinlee7250
Kevinlee7250 merged commit 67cab1a into latestAug 24, 2026
3 checks passed
github-actionsBot added a commit that referenced this pull request Aug 25, 2026
github-actionsBot added a commit that referenced this pull request Aug 26, 2026
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@Kevinlee7250@claude