개발 블로그
회고 5 분 소요

아이디어 사이클 회고 #3 — Stage 5에 Playwright MCP를 박은 이유, abort 없이 매일은 못 굴린다

automation claude-code playwright mcp qa llm-pipeline

결론부터

LLM이 만든 HTML을 lint만 통과시켜서 그대로 푸시하면, 콘솔 에러가 있는 사이클·모바일에서 가로 스크롤이 터지는 사이클·두 번째 버튼이 안 먹는 사이클이 며칠에 한 번씩 그대로 라이브에 올라간다. 그래서 Stage 5에 Playwright MCP를 박았다 — 실제 브라우저로 페이지를 띄우고, 스냅샷 찍고, 인터랙션 한 사이클을 끝까지 클릭해보고, 콘솔을 읽고, 모바일 폭으로 리사이즈해 본 뒤, 한 가지라도 hard failure면 커밋 전에 abort하고 텔레그램으로 알린다.

매일 자동으로 굴리는 파이프라인은 abort 경로가 없으면 결국 망가진 결과물이 누적된다. Stage 5는 그 끊어내는 칼이다.

.mcp.json — Playwright를 stage 안으로

설정은 한 파일이 전부다. claude CLI는 프로젝트 루트의 .mcp.json을 자동으로 읽어서 stdio MCP 서버를 띄운다.

.mcp.json
{
  "mcpServers": {
    "playwright": {
      "type": "stdio",
      "command": "npx.cmd",
      "args": ["-y", "@playwright/mcp@latest"],
      "env": {}
    }
  }
}

이 한 블록이 있으면 Stage 5의 LLM이 mcp__playwright__browser_navigate, browser_snapshot, browser_click, browser_console_messages, browser_resize 같은 툴을 실제로 호출할 수 있다. 검증 시나리오는 4단계로 고정돼 있다 — (1) 페이지 navigate + snapshot, (2) 콘솔 메시지에서 error/warning 추출, (3) 메인 인터랙션 + 한 단계 깊은 sub-flow까지 클릭(레벨 전환·상태 저장·언락 같은 진짜 동작이 일어나는지), (4) 375x667로 리사이즈해 모바일 가독성 확인.

처음엔 정적 분석으로 충분할 줄 알았다. 아니었다. lint는 "탐지 가능한 위반"만 잡는다 — 두 번째 레벨로 넘어갈 때 이벤트 리스너가 안 붙는 버그, 폰트가 system fallback으로 빠지는 케이스,

100vw가 모바일에서 가로 스크롤바를 트리거하는 클래식. 이런 건 실제로 띄워봐야 보인다.

3계층 텔레그램 알림 — 실패해도 침묵하지 않는다

매일 자동 실행의 가장 큰 함정은 실패가 조용한 것이다. 그래서 사이클은 셋 중 하나로 끝난다. 모두 같은 forum topic(TELEGRAM_CHAT_ID + TELEGRAM_THREAD_ID)으로 떨어진다 — 설정은 D:/playground/idea-cycles/.env에서 읽고 토큰은 절대 로그에 안 남긴다.

계층트리거발신자메시지
1정상 완료LLM agent (Stage 6 끝)🧪 idea-cycle <date> + 제목 + 데모 URL
2유료 기술 검토 필요LLM agent (Stage 1)⏸️ idea-cycle <date> 일시정지 — 커밋 안 함
3스크립트 자체 크래시PS 래퍼영문 [!] cycle script crashed - exit <code>

3계층은 LLM 자체가 죽은 케이스를 잡기 위해 PS 래퍼(scripts/run-cycle.ps1)에 따로 박혀 있다 — claude CLI가 비정상 종료하면 LLM은 메시지를 보낼 기회가 없으니, 래퍼가 .env를 직접 읽어 fallback alert을 친다.

scripts/run-cycle.ps1
if ($exitCode -ne 0) {
    $envFile = Join-Path $repo ".env"
    $envText = Get-Content -Raw $envFile -Encoding UTF8
    $token = ([regex]::Match($envText, 'TELEGRAM_BOT_TOKEN=(\S+)')).Groups[1].Value
    $chatId = ([regex]::Match($envText, 'TELEGRAM_CHAT_ID=(\S+)')).Groups[1].Value
    $threadId = ([regex]::Match($envText, 'TELEGRAM_THREAD_ID=(\S+)')).Groups[1].Value
    # ... POST to api.telegram.org/bot<token>/sendMessage
}

"조용한 실패"는 매일 자동화의 적이다. 처음 며칠은 알림이 안 와서 "오, 잘 돌고 있나봐" 했더니, 사실은 Task Scheduler가 PowerShell 5.1로 WinGet symlink된

claude.exe를 못 띄워서 며칠째 죽고 있었다. 그래서 PS 래퍼는 항상 pwsh.exe(PS 7+)로 실행하도록 등록했고, 크래시 fallback alert을 박았다.

QA의 hard failure든, 유료 기술이 필요한 ABORT든, 스크립트 자체 크래시든 — 어떤 실패도 텔레그램까지는 도달한다. 그게 안 도달하면 그때만 scripts/logs/<ts>.log를 본다. 매일 보지 않는다.

다음 편 예고

다음 편은 6단계 중 가장 중요한 단계 — Picker가 사실상 사이클 품질을 결정한다는 걸 30편쯤 만들고 알았다는 회고다. 5축 점수의 각 축이 왜 그 5개인지, 그리고 같은 실수를 반복하지 않게 만드는 Stage 0의 recent_critiques 메모리 트릭에 대해 이야기한다.