<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Perplexity on ICE-ICE-BEAR-BLOG</title><link>https://ice-ice-bear.github.io/ko/tags/perplexity/</link><description>Recent content in Perplexity on ICE-ICE-BEAR-BLOG</description><generator>Hugo -- gohugo.io</generator><language>ko</language><lastBuildDate>Fri, 03 Apr 2026 00:00:00 +0900</lastBuildDate><atom:link href="https://ice-ice-bear.github.io/ko/tags/perplexity/index.xml" rel="self" type="application/rss+xml"/><item><title>Log-Blog 개발기 #6 — 이중 언어 셋업, CDP 신뢰성, 마켓플레이스 마이그레이션</title><link>https://ice-ice-bear.github.io/ko/posts/2026-04-03-log-blog-dev6/</link><pubDate>Fri, 03 Apr 2026 00:00:00 +0900</pubDate><guid>https://ice-ice-bear.github.io/ko/posts/2026-04-03-log-blog-dev6/</guid><description>&lt;img src="https://ice-ice-bear.github.io/" alt="Featured image of post Log-Blog 개발기 #6 — 이중 언어 셋업, CDP 신뢰성, 마켓플레이스 마이그레이션" /&gt;&lt;h2 id="개요"&gt;개요
&lt;/h2&gt;&lt;p&gt;&lt;a class="link" href="https://ice-ice-bear.github.io/ko/posts/2026-04-02-log-blog-dev5/" &gt;이전 글: Log-Blog 개발기 #5&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;#5에서 Firecrawl deep docs 통합과 이중 언어 블로그 배포 파이프라인을 구현했다면, 이번 #6은 그 여파를 정리하는 회차다. 블로그 구조가 &lt;code&gt;content/ko/posts/&lt;/code&gt;와 &lt;code&gt;content/en/posts/&lt;/code&gt;로 바뀐 뒤, 새 사용자도 처음부터 이 구조를 설정할 수 있도록 &lt;strong&gt;setup 스킬을 확장&lt;/strong&gt;했다. 동시에 실제 운영 중에 마주친 &lt;strong&gt;AI 채팅 CDP 연결 경쟁 조건&lt;/strong&gt;을 수정하고, &lt;strong&gt;Perplexity 노이즈 URL 필터&lt;/strong&gt;를 추가했다. 플러그인은 글로벌 설치 방식에서 &lt;strong&gt;marketplace 기반 설치&lt;/strong&gt;로 마이그레이션했고, 버전은 0.2.0을 거쳐 0.2.1로 올렸다.&lt;/p&gt;
&lt;hr&gt;
&lt;pre class="mermaid" style="visibility:hidden"&gt;graph TD
 A["log-blog #6 변경사항"] --&gt; B["이중 언어 셋업 스킬"]
 A --&gt; C["CDP 신뢰성 개선"]
 A --&gt; D["플러그인 마켓플레이스 마이그레이션"]
 A --&gt; E["README 문서화"]

 B --&gt; B1["Phase 3A: 다국어 Hugo &amp;lt;br/&amp;gt; languages: 블록 생성"]
 B --&gt; B2["Phase 3B: 기존 블로그 &amp;lt;br/&amp;gt; 누락 languages: 감지"]
 B --&gt; B3["publisher --language 라우팅"]
 B --&gt; B4["post_advisor: 중복 제거"]

 C --&gt; C1["CDP 네비게이션 재시도 &amp;lt;br/&amp;gt; (race condition 수정)"]
 C --&gt; C2["Perplexity /search/new &amp;lt;br/&amp;gt; 노이즈 필터"]
 C --&gt; C3["에러 메시지 개선"]

 D --&gt; D1["0.2.0: 이중 언어 기능"]
 D --&gt; D2["0.2.1: CDP 수정"]&lt;/pre&gt;&lt;hr&gt;
&lt;h2 id="이중-언어-hugo-셋업-스킬-확장"&gt;이중 언어 Hugo 셋업 스킬 확장
&lt;/h2&gt;&lt;h3 id="배경"&gt;배경
&lt;/h3&gt;&lt;p&gt;#5에서 블로그 레포를 &lt;code&gt;content/ko/posts/&lt;/code&gt;와 &lt;code&gt;content/en/posts/&lt;/code&gt;로 나누고 12개 포스트를 이중 언어로 배포했다. 그런데 막상 &lt;code&gt;/logblog:setup&lt;/code&gt;으로 새 블로그를 만들면 이 구조를 전혀 생성하지 못했다. setup 스킬은 단일 언어 &lt;code&gt;content/posts/&lt;/code&gt; 구조만 알고 있었기 때문이다. 새 사용자가 플러그인을 설치해도 이중 언어 워크플로를 바로 시작할 수 없는 상황이었다.&lt;/p&gt;
&lt;h3 id="구현--phase-3a-신규-블로그-이중-언어-설정"&gt;구현 — Phase 3A: 신규 블로그 이중 언어 설정
&lt;/h3&gt;&lt;p&gt;setup 스킬 질문 흐름을 개편했다. 이제 Hugo 사이트 생성 단계에서 세 가지를 묻는다:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;블로그 이름&lt;/li&gt;
&lt;li&gt;기본 언어 (&lt;code&gt;en&lt;/code&gt;/&lt;code&gt;ko&lt;/code&gt;, 기본값: &lt;code&gt;en&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;다국어 지원 여부&lt;/strong&gt; — Yes면 언어 목록을 입력 (예: &lt;code&gt;en,ko&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;다국어를 선택하면 &lt;code&gt;hugo.yaml&lt;/code&gt;에 Hugo 공식 &lt;code&gt;languages:&lt;/code&gt; 블록이 추가된다:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;languages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;en&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;languageName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;English&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;contentDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;content/en&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;social&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ko&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;languageName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;한국어&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;contentDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;content/ko&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;menu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;social&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;동시에 각 언어별 content 디렉토리와 초기 포스트를 생성한다. 영어는 &lt;code&gt;content/en/posts/hello-world.md&lt;/code&gt;, 한국어는 &lt;code&gt;content/ko/posts/hello-world.md&lt;/code&gt;로, 파일명이 같으면 Hugo가 자동으로 번역본으로 인식한다.&lt;/p&gt;
&lt;h3 id="구현--phase-3b-기존-블로그-마이그레이션-감지"&gt;구현 — Phase 3B: 기존 블로그 마이그레이션 감지
&lt;/h3&gt;&lt;p&gt;단순 생성보다 까다로운 케이스는 &lt;strong&gt;이미 다국어 디렉토리 구조는 있는데 Hugo config에 &lt;code&gt;languages:&lt;/code&gt; 블록이 없는&lt;/strong&gt; 경우다. 이 경우 Hugo가 언어별 디렉토리를 무시하고 언어 전환기도 동작하지 않는다.&lt;/p&gt;
&lt;p&gt;setup 스킬 Step 2.5가 이를 감지한다:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ls -d &lt;span class="s2"&gt;&amp;#34;{path}/content/ko/posts&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;{path}/content/en/posts&amp;#34;&lt;/span&gt; 2&amp;gt;/dev/null
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;grep -c &lt;span class="s2"&gt;&amp;#34;^languages:&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;{path}/hugo.yaml&amp;#34;&lt;/span&gt; 2&amp;gt;/dev/null
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;디렉토리는 있는데 &lt;code&gt;languages:&lt;/code&gt; 블록이 없으면 경고 후 추가 여부를 묻는다. 사용자가 동의하면 기존 설정을 보존하면서 &lt;code&gt;languages:&lt;/code&gt; 섹션만 삽입한다.&lt;/p&gt;
&lt;h3 id="publisher와-post_advisor-연동"&gt;publisher와 post_advisor 연동
&lt;/h3&gt;&lt;p&gt;setup 스킬 변경과 함께 &lt;code&gt;publisher.py&lt;/code&gt;에도 &lt;code&gt;--language&lt;/code&gt; 파라미터를 추가했다. 이 파라미터가 전달되면 &lt;code&gt;config.yaml&lt;/code&gt;의 &lt;code&gt;language_content_dirs&lt;/code&gt; 매핑에서 해당 언어의 content 경로를 찾아 라우팅한다:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;content_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blog&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content_path_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;language&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;post_advisor.py&lt;/code&gt;도 수정했다. 기존에는 &lt;code&gt;content_dir&lt;/code&gt; 하나만 스캔했는데, 이제는 &lt;code&gt;language_content_dirs&lt;/code&gt;의 모든 경로를 스캔하되 동일 파일명의 중복을 제거한다. 이중 언어 블로그에서 &lt;code&gt;scan&lt;/code&gt; 명령이 한쪽 언어 포스트만 보는 문제가 해결됐다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="ai-채팅-cdp-신뢰성-개선"&gt;AI 채팅 CDP 신뢰성 개선
&lt;/h2&gt;&lt;h3 id="문제-cdp-네비게이션-경쟁-조건"&gt;문제: CDP 네비게이션 경쟁 조건
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;uv run log-blog chrome-cdp&lt;/code&gt;로 Chrome을 CDP 모드로 실행하면, 이미 탭이 열려 있는 상태에서 Playwright가 새 페이지를 만들고 URL로 이동할 때 &amp;ldquo;navigation interrupted&amp;rdquo; 오류가 간헐적으로 발생했다. Chrome 탭 간 이벤트 충돌이 원인이다.&lt;/p&gt;
&lt;p&gt;수정 전 코드는 단순히 한 번 시도하고 실패하면 &lt;code&gt;None&lt;/code&gt;을 반환했다:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wait_until&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;domcontentloaded&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timeout_ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="해결-재시도-로직"&gt;해결: 재시도 로직
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;_NAV_RETRIES = 2&lt;/code&gt; 상수를 추가하고, &lt;code&gt;&amp;quot;interrupted&amp;quot;&lt;/code&gt; 문자열이 에러 메시지에 포함된 경우에만 재시도한다. 재시도 사이에는 500ms 대기로 탭 이벤트가 안정되길 기다린다:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;_NAV_RETRIES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="c1"&gt;# retry count for CDP navigation race conditions&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_NAV_RETRIES&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wait_until&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;domcontentloaded&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timeout_ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;last_err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;nav_err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;last_err&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nav_err&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;_NAV_RETRIES&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;interrupted&amp;#34;&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nav_err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;CDP navigation interrupted (attempt &lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt;), retrying&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wait_for_timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;raise&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&amp;ldquo;interrupted&amp;quot;가 아닌 다른 오류(타임아웃, 네트워크 에러 등)는 즉시 raise해서 불필요한 재시도를 방지한다.&lt;/p&gt;
&lt;h3 id="perplexity-노이즈-필터"&gt;Perplexity 노이즈 필터
&lt;/h3&gt;&lt;p&gt;Perplexity 방문 기록에 실제 대화 URL(&lt;code&gt;perplexity.ai/search/...&lt;/code&gt;) 외에 새 검색 페이지(&lt;code&gt;perplexity.ai/search/new&lt;/code&gt;)가 섞여 들어왔다. 이 URL은 콘텐츠가 없는 랜딩 페이지인데, 기존 분류기가 이를 &lt;code&gt;ai_chat_perplexity&lt;/code&gt;로 잘못 분류해 쓸모없는 CDP fetch를 시도했다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;_AI_NOISE_PATTERNS&lt;/code&gt; 리스트에 한 줄을 추가해 필터링했다:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;perplexity\.ai/search/new(?:[?#]|$)&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;# &amp;#34;new search&amp;#34; landing page&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="에러-메시지-개선"&gt;에러 메시지 개선
&lt;/h3&gt;&lt;p&gt;CDP fetch 실패 시 로그가 단순히 &lt;code&gt;&amp;quot;AI chat fetch failed for URL: error&amp;quot;&lt;/code&gt; 였다. 원인을 알기 어렵다. 이제는 실행 가능한 조치를 포함한 메시지를 출력한다:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;warning&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;AI chat fetch failed for &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt; (&lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;): &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;. &amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Ensure Chrome is running with: uv run log-blog chrome-cdp&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="플러그인-마켓플레이스-마이그레이션"&gt;플러그인 마켓플레이스 마이그레이션
&lt;/h2&gt;&lt;h3 id="배경-글로벌-설치의-한계"&gt;배경: 글로벌 설치의 한계
&lt;/h3&gt;&lt;p&gt;이전에는 플러그인을 &lt;code&gt;~/.claude/plugins/logblog/&lt;/code&gt;에 직접 설치하는 방식이었다. 이 방식의 문제는 업데이트 감지가 플러그인 내 &lt;code&gt;plugin.json&lt;/code&gt;의 &lt;code&gt;version&lt;/code&gt; 문자열 비교에 의존한다는 점이다. 버전을 올리지 않으면 새 기능이 배포돼도 &lt;code&gt;/plugin&lt;/code&gt;이 &amp;ldquo;최신 버전&amp;rdquo; 으로 판단해 업데이트를 건너뛴다.&lt;/p&gt;
&lt;p&gt;#5에서 Firecrawl, 이중 언어 지원 등 15개 커밋을 쌓았지만 &lt;code&gt;version&lt;/code&gt;은 &lt;code&gt;&amp;quot;0.1.0&amp;quot;&lt;/code&gt; 그대로였다. 이를 발견한 후 marketplace 기반 설치로 전환했다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;설치 위치: &lt;code&gt;~/.claude/plugins/marketplaces/logblog/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;업데이트: marketplace의 버전 체계를 통해 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="버전-체계-020과-021"&gt;버전 체계: 0.2.0과 0.2.1
&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;0.2.0&lt;/strong&gt; — Firecrawl deep docs 통합, 이중 언어 블로그 지원, setup 스킬 다국어 확장, publisher &lt;code&gt;--language&lt;/code&gt; 라우팅 등 새 기능 묶음이라 minor 버전 업으로 결정했다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;0.2.1&lt;/strong&gt; — CDP 신뢰성 수정과 Perplexity 노이즈 필터. 새 기능이 아니라 버그 수정이므로 patch 버전 업.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;marketplace.json&lt;/code&gt;의 플러그인 항목도 최신 버전 정보를 반영하도록 업데이트했다.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="readme-문서화"&gt;README 문서화
&lt;/h2&gt;&lt;p&gt;이번 세션에서 README가 크게 업데이트됐다. 추가된 주요 섹션:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;이중 언어 워크플로&lt;/strong&gt;: 한국어 포스트 작성 → 영어 번역 → 각 &lt;code&gt;content/{lang}/posts/&lt;/code&gt;에 배포하는 전체 흐름&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Firecrawl 통합&lt;/strong&gt;: &lt;code&gt;--deep&lt;/code&gt; 플래그로 문서 사이트 전체 크롤링하는 방법&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dev Log 모드&lt;/strong&gt;: 세션 데이터에서 dev log 포스트를 생성하는 스킬 사용법&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AI 채팅 fetching&lt;/strong&gt;: &lt;code&gt;chrome-cdp&lt;/code&gt; 명령으로 Chrome을 CDP 모드로 실행하는 법, 각 서비스별 &lt;code&gt;auth_profile&lt;/code&gt; 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="커밋-로그"&gt;커밋 로그
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;메시지&lt;/th&gt;
 &lt;th&gt;변경&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;docs: update README with bilingual workflow, Firecrawl, dev logs, and AI chat features&lt;/td&gt;
 &lt;td&gt;+31 -7&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;chore: bump plugin version to 0.2.0&lt;/td&gt;
 &lt;td&gt;+1 -1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;feat: add multi-language Hugo setup to setup skill and publisher&lt;/td&gt;
 &lt;td&gt;+226 -26&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;chore: bump plugin version to 0.2.1&lt;/td&gt;
 &lt;td&gt;+1 -1&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;fix: improve AI chat CDP reliability and Perplexity noise filter&lt;/td&gt;
 &lt;td&gt;+30 -3&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;hr&gt;
&lt;h2 id="인사이트"&gt;인사이트
&lt;/h2&gt;&lt;p&gt;이번 회차는 &amp;ldquo;기능을 만들었더니 인프라가 따라오지 못하는&amp;rdquo; 전형적인 패턴이었다. #5에서 이중 언어 블로그를 구현하고 수동으로 레포를 재구성했는데, setup 스킬이 이를 지원하지 못해 새 사용자가 같은 결과를 얻을 수 없었다. 기능과 셋업 경험을 동기화하는 작업이 항상 뒤를 따라야 한다는 점을 다시 확인했다.&lt;/p&gt;
&lt;p&gt;CDP 연결 경쟁 조건은 재현이 어려운 종류의 버그다. Chrome의 탭 이벤트 타이밍에 의존하기 때문에 매번 나타나지 않는다. &amp;ldquo;interrupted&amp;rdquo; 문자열로만 재시도를 트리거하는 좁은 조건이 오히려 올바른 선택이었다 — 타임아웃 같은 다른 오류까지 재시도하면 느린 네트워크에서 지연이 두 배로 늘어난다.&lt;/p&gt;
&lt;p&gt;플러그인 버전 관리는 단순해 보이지만 배포 신뢰성의 핵심이다. 버전 문자열을 올리지 않으면 사용자가 업데이트를 받지 못한다. marketplace 기반 관리로 전환하면서 이 프로세스가 더 명시적이 됐다.&lt;/p&gt;
&lt;p&gt;log-blog 자체가 log-blog로 기록되는 메타적 상황을 즐기고 있다. 포스트를 쓰다 발견한 불편함이 다음 커밋의 동기가 되고, 그 커밋이 다음 포스트의 내용이 된다.&lt;/p&gt;</description></item></channel></rss>