예약 발행이 또 안 떴습니다 — 빈 커밋은 밀기 전에 접었습니다
2026-09-03 오전, 공개일이 된 글이 라이브에 없었습니다. 예약 발행 스케줄은 이틀 전에 한 번 손봐둔 참이었는데도 그랬습니다.
미래 날짜 글이 빌드에서 빠진다는 것, 날짜가 됐다고 저절로 나타나지 않고
빌드가 한 번 더 돌아야 한다는 것은 먼저 확인해서 적어뒀습니다.
그래서 워크플로에 schedule 트리거를 달아둔 상태였습니다.
이 글은 그 설계가 또 실패했을 때 어떻게 되살리는가에 대한 기록입니다.
빠진 것이 글 하나인지 배포 전체인지
splitting-the-repo 는 date: 2026-09-03 이고
publishDate 가 없어 date 로 폴백한 글입니다. 09:00 KST 에 공개 자격을 얻고
1차 cron 이 10:23 KST 에 도는데, 11:44 KST 에 봤을 때 없었습니다.
$ curl -o /dev/null -w '%{http_code}' https://birchholt.com/posts/splitting-the-repo/
404
404 하나로는 “배포는 돌았는데 그 글만 빠졌다"와 “배포가 아예 안 돌았다"가 갈리지 않습니다.
그래서 라이브 sitemap.xml 의 <loc> 목록을 같이 봤습니다. privacy 까지 포함해 9건인데
그 주소가 없었고, 올라가 있는 결과물 자체가 그 글을 모르는 상태였습니다.
한 주소만 찍는 것이 왜 확인이 아닌지는 먼저 적어뒀습니다.
푸시는 돼 있었고(git rev-parse HEAD origin/main 두 값이 같았습니다) 로컬 빌드에도
그 글이 들어 있었습니다. 자격도 커밋도 다 됐는데 라이브만 옛날 결과물이었습니다.
정각을 피해도 건너뜁니다
이게 처음이 아니었습니다. 2026-09-01 에 '0 1 * * *' 가 발화하지 않아 예약 글이
안 올라갔고, 그때 근거로 삼은 것은 GitHub 공식 문서의 이 문장이었습니다.
“The
scheduleevent can be delayed during periods of high loads of GitHub Actions workflow runs. High load times include the start of every hour.”
그래서 정각을 피해 '23 1 * * *' 로 옮겼고, 그것만으로는 부족하다고 보고
하루 3회로 늘려뒀습니다.
| cron | KST | 역할 |
|---|---|---|
23 1 * * * | 10:23 | 1차입니다. 미래 글은 09:00 KST 부터 자격이 생깁니다 |
23 4 * * * | 13:23 | 1차가 안 떴을 때 잡습니다 |
23 7 * * * | 16:23 | 2차도 안 떴을 때 잡습니다 |
그 상태에서 2026-09-03 에 1차가 또 안 떴습니다. cron 이 안 뜬 두 번째 사례이고, 정각을 피한 뒤로는 첫 사례입니다. 정각 회피는 확률을 낮출 뿐이고, GitHub 스케줄은 보장이 아니라 best-effort 입니다. 백업 cron 두 개를 둔 판단 자체는 옳았습니다. 다만 이번에 그것들이 일한 건 아닙니다 — 13:23 까지 기다릴 생각이 없어서 2차가 돌기 전에 직접 되살렸습니다.
1차가 「건너뛴」 것인지 「돌다가 실패한」 것인지는 구분하지 못했습니다.
저장소가 비공개이고 gh CLI 가 다른 계정으로 로그인돼 있어 실행 이력을 못 봤습니다.
paths-ignore 가 걸린 워크플로의 복구 경로
먼저 떠오른 건 git commit --allow-empty 였습니다. 내용은 안 건드리고
push 만 발생시키면 될 것 같았습니다. 밀기 전에 워크플로를 열어봤고, 거기서 접었습니다.
on:
push:
branches: [main]
paths-ignore:
- 'README.md'
- '.gitignore'
paths-ignore 가 있으면 GitHub 은 그 push 의 변경 파일 목록을 필터에 겁니다.
변경 파일이 0건이면 필터를 통과하는 파일도 0건이라 워크플로가 뜨지 않습니다.
다만 이건 설정을 읽고 내린 판단이지, 밀어보고 확인한 것이 아닙니다.
그래서 되살리는 경로를 둘로 봤습니다.
| 방법 | 조건 |
|---|---|
Actions 탭에서 workflow_dispatch 수동 실행 | 워크플로에 그 트리거가 있어야 합니다 |
| 변경이 든 커밋을 push | paths-ignore 에 없는 파일이 하나는 바뀌어야 합니다 |
저는 두 번째를 골랐습니다. 마침 이 사건 자체를 워크플로 주석에 남길 참이어서,
deploy.yml 에 경위를 적은 커밋이 그대로 트리거가 됐습니다.
푸시하고 라이브를 15초 간격으로 찍었습니다.
[11:49:09] try 3 → 404
[11:49:25] try 4 → 200
푸시부터 반영까지 1분 남짓이었습니다.
타임존 탓인 줄 알았는데 아니었습니다
저는 로컬(KST)과 Actions(UTC)의 판정이 9시간 어긋나서 생긴 일이라고 봤습니다. 로컬에서 공개일이 지난 글이 UTC 기준으로는 아직 미래라면 증상이 그대로 설명되니까요. 같은 트리를 타임존만 바꿔 세 번 빌드했습니다(02:47 UTC).
| TZ | 빌드 결과 | 그 타임존이 적용됐다면 |
|---|---|---|
UTC | 7편, 그 글 포함입니다 | 포함이 맞습니다 |
Asia/Seoul (UTC+9) | 7편, 그 글 포함입니다 | 포함이 맞습니다 |
Pacific/Midway (UTC-11) | 7편, 그 글 포함입니다 | 빠져야 합니다 |
셋째 줄이 답을 줬습니다. UTC-11 에서 시각 없는 date: 2026-09-03 은 11:00 UTC 가 되어
측정 시점에는 아직 미래입니다. 빠져야 하는데 빠지지 않았습니다.
Hugo 는 TZ 환경변수를 보지 않습니다. hugo.toml 에 timeZone 이 없으면
시각 없는 날짜를 UTC 로 고정해서 읽습니다. 로컬과 CI 가 애초에 어긋나지 않으니,
이번 원인은 타임존이 섞인 것이 아니라 순수한 cron 미발화였습니다.
판정을 실제로 움직일 수 있는 값은 셸의 TZ 가 아니라 hugo.toml 의 timeZone 인데,
그건 넣어보고 재보지 않았습니다. timeZone 을 추가할 이유가 없다는 결론은
「양쪽이 어긋나지 않는다」까지만 근거가 있습니다.
이 실측을 워크플로 주석에 남길 때는 처음에 Pacific/Midway 한 건만 적었습니다.
나중에 세 건을 다 적어뒀습니다 — 한 건만 남겨두면 이 결론을 나중에 다시 의심할 때
재현 범위를 알 수가 없습니다.
빌드 제외를 결정하는 것은 publishDate 이고, 없으면 date 로 폴백합니다.
시각이 없으면 양쪽 다 00:00 UTC 로 읽히고, 그게 KST 로 09:00 입니다.
1차 cron 을 10:23 KST 에 둔 것이 그 때문입니다 — 자격이 생기고 나서 도는 시각이어야 합니다.
정리
- 예약 글이 안 보일 때 404 하나는 “그 글만 빠졌다"와 “빌드가 안 돌았다"를 가르지 못하는 신호였습니다
- 정각 회피는 확률을 낮출 뿐입니다. GitHub 스케줄은 보장이 아니라 best-effort 라 백업 cron 이 필요하지만, 이번에는 그게 돌기 전에 제가 먼저 되살렸습니다
paths-ignore가 걸린 워크플로는 빈 커밋으로 못 깨웁니다. 밀어보지 않고 설정으로 내린 판단이라, 되살릴 때는paths-ignore에 없는 파일을 하나 바꿨습니다- 급할 때 확실한 복구 경로는
workflow_dispatch수동 실행이고, 이번에는 커밋 push 로 1분 남짓 만에 돌아왔습니다 - 타임존이 원인인 줄 알았지만 Hugo 는 셸의
TZ를 보지 않았습니다. 대신timeZone을 넣으면 어떻게 달라지는지는 확인하지 못했습니다