Never forget to enter the Stern Grove lottery again!
Browser automation with Playwright, Python, GitHub Actions, and Entire to auto-enter San Francisco Stern Grove concert lotteries each week!
개요 #
샌프란시스코의 여름에는 매주 Stern Grove 음악 축제의 무료 공연 추첨이 열린다. 문제는 응모 기간을 매번 놓친다는 것. 개발자 Lizzie Siegle은 캘린더 알림 대신 자동 응모 봇을 만들기로 했다. Python 스크립트가 GitHub Actions에서 매주 돌면서 Playwright로 축제 사이트를 훑고, 추첨에 알아서 응모한다.
흥미로운 건 결과물보다 만드는 과정이다. 코딩 에이전트에게 원하는 동작을 설명하자 Entire가 모든 프롬프트, 도구 호출, 출력을 기록으로 남겼다. 덕분에 이 봇이 어떻게 만들어졌는지 처음부터 끝까지 되짚어 볼 수 있는 감사 가능한 기록이 생겼다.
메시지 한 줄에서 시작했다 #
프로젝트는 코딩 에이전트에게 보낸 메시지 한 줄에서 출발했다. 글쓴이는 Claude Code를 썼지만 다른 에이전트도 상관없다고 말한다. 먼저 게임의 규칙을 알려줬다.
Stern Grove 추첨은 각 공연 6주 전 오전 10시에 열려 일주일 동안 응모를 받는다…
응모 버튼의 정확한 HTML도 함께 건넸다. Stern Grove는 Tixologi라는 발권 시스템을 쓰기 때문에, 에이전트가 무엇을 상대하는지 알아야 했다.
그다음 GitHub Action이 접근할 비밀값들을 일러줬다.
- 이름
- 도시
- 이메일
- Resend API 키 (알림용)
- 발신 이메일 주소
- 우편번호
이 값들은 전부 GitHub Actions의 암호화된 시크릿으로 저장하고 저장소에는 절대 커밋하지 않는다. 이 제약을 처음부터 분명히 못 박았다.
에이전트가 제안한 구조 #
에이전트는 구현에 들어가기 전에 명세를 살피고 한 가지를 되물은 뒤 깔끔한 설계를 내놨다.
lottery.py— GitHub Actions 크론 스케줄이 호출하는 진입점browser.py— Playwright 로직 전부. 웹 스크래핑과 브라우저 자동화 담당state.py—entered_lotteries.json을 읽고 써서 중복 응모를 막는다notify.py— Resend로 성공·실패 이메일 발송
영리했던 부분은 상태 관리다. 일주일에 한 번 도는 작업 때문에 데이터베이스를 세우는 대신, 응모 기록을 JSON 파일에 담아 매 실행 후 저장소에 다시 커밋한다.
{
"entered": [
{ "event_id": "abc123", "show": "Show Name", "entered_at": "2025-06-21T10:02:00Z" }
]
}크론 스케줄은 워크플로 파일에 들어간다.
# .github/workflows/lottery.yml
on:
schedule:
- cron: "0 17 * * 1" # 매주 점검
workflow_dispatch:자동화 전에 먼저 들여다봤다 #
짚어 둘 만한 단계가 하나 있다. 브라우저 로직을 건드리기 전에 에이전트는 실제 Stern Grove 페이지를 직접 점검했다. 추측 대신 window.tixologiWidget.concerts를 들여다보며 데이터가 실제로 어떤 모양인지 학습한 것이다.
Tixologi는 발권·이벤트 관리 플랫폼이다. 이 점검 과정에서 정확한 필드 이름, ISO 8601 날짜 형식, 이벤트 ID 구조가 그대로 드러났다. 다음 콘솔 로그 한 줄이 나중의 디버깅 시간을 크게 줄였다.
// 페이지가 실제로 노출하는 데이터
window.tixologyWidget.concerts
// → [{ eventId, name, startDate: "2025-07-13T19:00:00Z", ... }]가정이 아니라 실제로 관찰한 데이터에 구현을 묶어 두면, 첫 실전 실행에서 바로 동작하는 코드와 운영에서 조용히 실패하는 코드가 갈린다.
상태를 저장소에 되밀어 넣기 #
state.py는 JSON을 읽기만 하지 않는다. 응모에 성공하면 GitHub 개인 액세스 토큰(PAT)으로 변경분을 커밋하고 푸시한다. 다음 실행이 이미 끝난 일을 알도록 하기 위해서다.
def commit_state(token: str) -> None:
subprocess.run(["git", "add", "entered_lotteries.json"], check=True)
subprocess.run(["git", "commit", "-m", "Update entered lotteries"], check=True)
subprocess.run(["git", "push"], check=True)리뷰 루프가 실제 버그를 잡아냈다 #
에이전트가 notify.py를 추가하자 리뷰어 서브 에이전트가 곧바로 두 가지 문제를 지적했다.
logging모듈 대신print()문을 쓰고 있었다.- Resend API 키 초기화가 중복돼 있었다.
에이전트는 다음 작업으로 넘어가기 전에 두 가지를 모두 고쳤다. 작업 → 리뷰 루프가 제 역할을 한 셈이다. 보통 몇 주 뒤에야 드러나는 종류의 허술함을 그 자리에서 걸러냈다.
import logging
logger = logging.getLogger(__name__)
resend.api_key = os.environ["RESEND_API_KEY"] # 한 번만 초기화
def notify_success(show: str) -> None:
logger.info("Entered lottery for %s", show)
resend.Emails.send({ ... })모든 프롬프트와 도구 호출을 되짚는 법 #
글쓴이가 진짜 쓸모 있다고 느낀 대목은 이 부분이다. 자동화가 어떻게 만들어졌는지 기억할 필요가 없었다. Entire가 전 과정을 잡아 뒀기 때문이다. Entire에서 세션은 AI 코딩 상호작용의 완전한 기록, 즉 모든 프롬프트와 응답, 도구 호출, 파일 변경을 담는다. 글쓴이는 위 세션 링크에서 자신의 기록을 공개했다.
탐색하는 방법은 이렇다.
- 세션 찾기. 세션은 체크포인트 상세 페이지의 Sessions 탭에 있다. 각 행에는 실행된 에이전트, 단계 수, 첫 프롬프트, 타임스탬프가 보인다. 클릭하면 타임라인이 열린다.
- 타임라인 읽기. 세션 타임라인은 대화를 순서대로 보여 준다. 글쓴이의 프롬프트, 에이전트의 응답, 펼칠 수 있는 도구 호출, 런타임 태그가 차례로 나온다. 메타데이터 패널은 모델, 소요 시간, 토큰 사용량을 요약한다.
- 도구 호출 펼치기. 도구 호출은 인라인 행으로 나타나며, 펼치면 정확한 인자와 결과를 볼 수 있다. 파일 편집의 경우 그 자리에서 diff까지 보여 준다.
- 불필요한 정보 거르기. 오른쪽 필터 영역에서 보일 항목을 켜고 끌 수 있다. 프롬프트, 응답, 중간 단계, 체크포인트, 도구 호출(파일 편집·bash·읽기 등으로 더 세분화)까지 토글된다. 고수준 흐름만 보고 싶을 때 유용하다.
- 서브 에이전트 들여다보기. 에이전트가 Claude Code의
Task도구로 리뷰어 서브 에이전트를 띄우면, Entire는 그것을 자체 트랜스크립트와 도구 호출을 가진 별도 세션으로 잡아 부모 체크포인트 합계에 묶는다. 앞서print()/logging 문제를 잡아낸 리뷰 루프를 바로 여기서 확인할 수 있다. - 코드로 바로 이동. 모든 세션은
Entire-Checkpoint트레일러로 Git 커밋과 연결돼, 대응하는 체크포인트나 GitHub의 커밋을 곧장 열 수 있다. 메타데이터 자체는entire/checkpoints/v1라는 별도 브랜치에 따로 두어 메인 히스토리를 깨끗하게 유지한다.
처음엔 동작하지 않았다 #
솔직히 첫 실전 실행은 실패했다. CI가 첫 시도에 협조하는 일은 드물고, 이번에도 예외는 아니었다. 환경 문제 두 가지가 발목을 잡았다.
1. Ubuntu 버전 드리프트. Playwright 호환을 위해 러너를 Ubuntu 22.04로 고정해야 했다. ubuntu-latest가 2025년 들어 조용히 Ubuntu 24.04가 되면서 libasound2 패키지 이름이 바뀐 탓이다.
2. Chromium 의존성. 결국 Playwright 번들 설치기에 기대지 않고, Ubuntu 24.04용 정확한 패키지 이름으로 Chromium 의존성을 직접 설치했다.
수정 전은 이랬다.
# 수정 전
- run: playwright install --with-deps chromium수정 후에는 명시적인 apt 설치와 가벼워진 Playwright 단계로 바뀌었다.
# 수정 후
- run: |
sudo apt-get update
sudo apt-get install -y libasound2t64 libnss3 libnspr4 # ...24.04용 정확한 이름
- run: playwright install chromium마침내 성공 #
결국 봇은 깔끔하게 돌았다. Stern Grove와 Resend로부터 확인 이메일이 곧장 도착했다. 최종 스택은 이렇게 맞물렸다.
- Playwright — 브라우저 자동화
- GitHub Actions — 매주 크론 스케줄과 시크릿 관리
- Resend — 성공·실패 알림
- 코딩 에이전트(Claude Code) — 실제 버그를 잡는 리뷰 루프와 함께 봇을 구축
- Entire — 모든 프롬프트와 도구 호출, 출력을 커밋과 연결해 기록
데이터베이스 없이 JSON 파일을 저장소에 되밀어 넣는 상태 관리, 추측 대신 실제 데이터를 먼저 관찰하는 습관, 그리고 사람 손이 가야 할 자리를 메우는 리뷰 루프. 작은 자동화 하나에도 운영에서 살아남는 코드의 패턴이 그대로 담겨 있다.
이 글은 위 출처를 바탕으로 한국 독자를 위해 재작성한 기사입니다. 원문의 사실과 수치에 근거하며, 별도의 견해를 포함하지 않습니다.

