릴리스를 돌렸는데 게이트가 활동 타임라인 문구 검사에서 떨어졌다. 그 릴리스는 타임라인 근처를 건드린 적이 없다. 기대값은 /오전 12:00/이고 받은 값은 "AM 12:00"이었다. 코드를 아무리 봐도 오전이라는 글자를 만드는 곳이 없어서 한참 헤맸다.

당연했다. 그 글자를 만드는 건 우리 코드가 아니라 Intl.DateTimeFormat이다. 코드가 그대로인데 결과가 달라졌으면 남은 변수는 런타임뿐이다. 저장소의.nvmrc는 22.14.0인데 셸의 node -v는 v24.13.1을 찍고 있었다.

01 / 재현

같은 한 줄을 버전별로 돌려 봤다

실제 코드가 쓰는 로케일과 옵션 그대로, 같은 시각 문자열을 여러 Node에서 포맷했다. 22.14.0은 "2026년 8월 27일 오전 12:00"을 냈고 24.13.1은 "2026년 8월 27일 AM 12:00"을 냈다. 여기까지는 예상대로였다.

근데 22.23.2도 AM을 냈다. 그러니까 이건 Node 24의 문제가 아니다. 갈리는 지점은 번들된 ICU 버전이다. 오전을 내는 쪽은 ICU 76.1 이하였고, AM을 내는 쪽은 전부 ICU 78.2에 CLDR 48이었다. ko 로케일의 오전·오후 표기가 그 데이터에서 바뀐 것이다.

이 사실이 가드 설계를 바꿨다. "22 이상"이나 "24 미만" 같은 범위 조건으로 걸렀으면 22.23.2를 그냥 통과시켰을 것이다. 우리가 고정해야 하는 건 메이저 버전이 아니라 특정 ICU를 싣고 있는 정확한 빌드였다.

  • v22.14.0 · ICU 76.1 → 오전 12:00
  • v22.23.2 · ICU 78.2 → AM 12:00
  • v24.13.1 · ICU 78.2 → AM 12:00
  • v25.8.0 · ICU 78.2 → AM 12:00
02 / 고친 것

게이트 40분이 아니라 첫 줄에서 막았다

커밋 f7648257에서 릴리스 스크립트 앞머리에 열세 줄을 넣었다..nvmrc를 읽어서 node -v와 정확히 같은지 본다. 다르면 현재 버전을 문장에 적어 주고 exit 64로 끝낸다. nvm use 명령까지 같이 찍는다.

위치가 핵심이다. 이 검사는 게이트 스위트를 부르기 전에 있다. 잘못된 런타임으로 시작하면 40분쯤 뒤에 로케일 문구 검사에서 떨어지는데, 그 실패 메시지는 릴리스와 아무 상관이 없어서 사람을 엉뚱한 코드로 보낸다. 앞에서 막으면 읽을 문장이 하나고 고칠 것도 하나다.

범위 비교가 아니라 문자열 일치로 둔 것도 의도다. 느슨하게 두면 언젠가 또 다른 ICU가 들어온 패치 버전이 통과한다. 저장소가 버전을 고정한 이유가 있으면 그 이유대로 정확히 요구하는 편이 낫다.

scripts/release-prod-direct.sh
bash
required_node_version="$(tr -d '[:space:]' < "${ROOT_DIR}/.nvmrc")"
actual_node_version="$(node -v 2>/dev/null || true)"
if [[ "$actual_node_version" != "v${required_node_version}" ]]; then
  echo "This release must run on Node v${required_node_version} (.nvmrc); the current shell has ${actual_node_version:-no node}." >&2
  echo "Run: nvm use ${required_node_version}" >&2
  exit 64
fi

코드 발췌 · 메이저 범위가 아니라.nvmrc와 정확히 같은 빌드만 통과시킨다.

03 / 문구의 주인

오전이라는 글자를 누가 만드는지 정해 둔다

앱 쪽 코드에도 오전·오후를 쓰는 곳이 있는데 거기는 멀쩡했다. 시각을 12로 나눠 문자열을 직접 붙이고 있었기 때문이다. 흔들린 건 ICU에 표기를 맡긴 쪽뿐이었다. 같은 화면 문구인데 주인이 달랐던 셈이다.

어느 쪽이 옳다고 말하기는 어렵다. ICU에 맡기면 로케일 데이터가 개선될 때 같이 좋아지지만 런타임을 고정해야 한다. 직접 만들면 런타임과 무관해지지만 로케일이 늘어날 때마다 우리가 감당해야 한다. 문제는 둘 중 어느 쪽도 정하지 않은 채 정규식으로 결과 문자열만 박아 두는 상태다.

이번엔 런타임을 고정하는 쪽을 골랐다. 검사는 그대로 두고 스크립트가 버전을 강제한다. 다만 로케일 출력 문자열을 정규식으로 고정한 검사가 어디에 몇 개 있는지는 알아 두게 됐다. 다음에 ICU가 또 움직이면 그 목록부터 볼 것이다.

로케일 출력 문자열을 정규식으로 박아 두면 그 검사는 ICU 데이터에 묶인다.

같은 코드가 런타임에 따라 다른 글자를 만든다

  1. 로케일 출력이 달라졌으면 코드보다 런타임의 ICU 버전을 먼저 본다
  2. ICU 78.2와 CLDR 48부터 ko 로케일의 오전·오후가 AM·PM으로 나온다
  3. 메이저 버전 범위가 아니라.nvmrc와 정확히 일치하는 빌드를 요구한다
  4. 런타임 검사는 긴 게이트 스위트 앞에 두어야 엉뚱한 실패를 읽지 않는다

이 글 수정 제보