Repository navigation
feat!: Node 최소 버전 24로 상향 + CI matrix [24] (EOL 런타임 탈출) #45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
hsleedevelop
merged 11 commits into
main
from
ao/harness-aijient-team-plugin-22/task-templates-flake
Aug 25, 2026
Merged
Changes from 7 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
09216bd
docs(task): node-test-runner-flake task 생성 — 원인 확정까지
chad-aijinet 1de978a
docs(task): 백포트 실측으로 "활성 LTS 이동 = flake 해소" 전제를 정정
chad-aijinet bf0f467
Merge branch 'ao/harness-aijient-team-plugin-22/boundary-perf-invaria…
chad-aijinet 51c3cce
feat!: 최소 Node 버전을 24로 올리고 CI matrix를 [24]로 이동
chad-aijinet 8bec36d
ci(test): annotation 패턴을 리포터 비의존으로 수정
chad-aijinet 5e9f291
docs(artifact): node 24 민감도 우려를 CI 실측으로 해소
chad-aijinet 74d1e8a
docs(artifact): PR #44 머지 직후 main이 이 flake로 빨개진 2회차 관측 기록
chad-aijinet 57520c3
fix(ci): AO 리뷰 조치 — 해소 선언 보류 + 런타임 기록, MAINTAINING.md 갱신
chad-aijinet 57ef864
docs(artifact): task 상태를 현재로 갱신 — AO 2차 리뷰 P2 조치
chad-aijinet 5bc8f47
docs(task): TCC 전체를 현재 working set 으로 재작성 — AO 3차 리뷰 P2 조치
chad-aijinet e6af169
docs(spec): 정책 서술을 확정 상태로 교체 — AO 4차 리뷰 P2 조치
chad-aijinet File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
217 changes: 217 additions & 0 deletions
217
docs/chad/node-test-runner-flake/node-test-runner-flake-artifact.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,217 @@ | ||
| # node-test-runner-flake — Artifact | ||
|
|
||
| *최종 결과물과 학습 내용을 기록한다.* | ||
|
|
||
| ## 결과 | ||
|
|
||
| **미착수 — 원인 확정까지만 진행했다.** 남은 것은 기술 문제가 아니라 지원 Node 범위 | ||
|
hsleedevelop marked this conversation as resolved.
Resolved
|
||
| **정책 결정**이며(plan 6단계), spec의 Ambiguity 게이트를 의도적으로 미통과 상태로 뒀다. | ||
|
|
||
| 한 줄 요약: `node:test` 러너의 업스트림 버그(nodejs/node#64061, `#processRawBuffer`가 | ||
| payload 길이를 부호 있는 정수로 읽음)다. 수정은 Node 26.7.0에 들어갔고 **node 18에는 | ||
| 영구히 도달하지 않는다 — 이미 EOL이다.** 상세는 아래 "조사 기록". | ||
|
|
||
| ## 조사 기록 (2026-08-25) — 구현 전, 원인 확정까지 | ||
|
|
||
| 이 task는 **구현이 시작되지 않은 상태**로 생성됐다. 아래는 원인 확정까지의 근거이며, | ||
| 남은 것은 기술 문제가 아니라 지원 범위 **정책 결정**이다 (plan 6단계). | ||
|
|
||
| ### 어떻게 발견됐나 | ||
|
|
||
| perf flake(PR #44)를 고치면서 workflow에 annotation 계측을 넣었다 — | ||
| 이 저장소를 유지보수하는 머신은 raw CI 로그를 받을 수 없기 때문이다 | ||
| (`*.blob.core.windows.net` 403). 계측을 넣은 **첫 실행**에서 곧바로 잡혔다: | ||
|
|
||
| ``` | ||
| not ok 38 - tests/task-templates.test.mjs | ||
| error: 'Unable to deserialize cloned data due to invalid or unsupported version.' | ||
| ``` | ||
|
|
||
| 계측이 없었다면 이 실패는 "test (18) failed, exit code 1"로만 보였을 것이고, | ||
| perf flake와 구분되지 않은 채 같은 원인으로 오인됐을 것이다. | ||
| **"CI가 무작위로 빨개진다"에는 최소 두 개의 서로 다른 원인이 섞여 있었다.** | ||
|
|
||
| ### 우리 코드가 아니라는 근거 | ||
|
|
||
| | 근거 | 내용 | | ||
| |---|---| | ||
| | 파일 내용 | `tests/task-templates.test.mjs`는 템플릿 문자열 assertion + fs 조작만. 자식 프로세스 스폰 없음, 직렬화 사용 없음 | | ||
| | 동일 코드 통과 이력 | 같은 테스트 코드로 앞선 실행(`af6ae4c7`)은 두 job 모두 통과 | | ||
| | 재실행 통과 | 실패 job만 재실행(attempt 2)하니 통과 → 전이성 | | ||
| | 스택 위치 | `node:internal/test_runner/runner`의 `#processRawBuffer` — 우리가 호출하지 않는 내부 함수 | | ||
| | 버전 편향 없음 | 과거 40회 실행 기준 실패율 node 18 7/39 · node 20 7/40 — 특정 버전 편향 아님 | | ||
|
|
||
| ### 업스트림 근본 원인 | ||
|
|
||
| - **nodejs/node#64061** — 증상 보고. `#processRawBuffer`에서 발생. "로컬 간헐적, CI 재현 잘 됨". | ||
| Appium은 Node 24.10.0에서 관측 — **18 전용 버그가 아니다.** | ||
| - **nodejs/node#64706** — 수정. payload 길이를 **부호 있는 정수**로 읽던 것을 | ||
| `>>> 0`으로 부호 없는 변환. `child_process`에 이미 쓰이던 패턴. | ||
| payload가 커져 최상위 비트가 서면 길이가 무효가 되어 역직렬화가 깨진다. | ||
| 동시 실행 worker가 결과를 스트리밍할 때 payload가 커지므로 CI에서 잘 터진다. | ||
| - 머지 2026-07-26 → **Node 26.7.0 (2026-08-05)** 릴리스에 포함. | ||
|
|
||
| ### 결정적 제약 — matrix가 전부 지원 종료 런타임이다 | ||
|
|
||
| nodejs/Release 공식 스케줄 기준 (2026-08-25 현재): | ||
|
|
||
| | 버전 | EOL | 상태 | 우리 사용처 | | ||
| |---|---|---|---| | ||
| | **18** | 2025-04-30 | **종료 (1년 4개월 경과)** | `test.yml` matrix · `engines: ">=18"` | | ||
| | **20** | 2026-04-30 | **종료 (4개월 경과)** | `test.yml` matrix · `release.yml` 발행 | | ||
| | 22 | 2027-04-30 | 활성 LTS | — | | ||
| | 24 | 2028-04-30 | 활성 LTS | — | | ||
|
|
||
| **node 18에는 이 수정이 절대 도달하지 않는다** — 이미 EOL이다. | ||
| 따라서 "node 18에서 이 flake를 고친다"는 업스트림 경로로 달성 불가능하며, | ||
| 이것이 후보 (A)(활성 LTS로 이동)를 권장하는 근거다. | ||
|
|
||
| 부수 발견: **릴리스 발행(`release.yml`)도 EOL 런타임(node 20)에서 돈다.** | ||
| 이 task의 원래 범위 밖이지만 같은 결정에 묶인다. | ||
|
|
||
| ### 정정 (2026-08-25) — 백포트 실측으로 초기 권장안을 뒤집었다 | ||
|
|
||
| 초기 보고는 "활성 LTS(22/24)로 이동하면 EOL 문제와 flake가 함께 해소된다"였다. | ||
| **틀렸다.** 오케스트레이터가 "수정이 26.7.0에 들어갔다면 22/24로 옮겨도 flake는 남는 것 아닌가"를 | ||
| 지적했고, 확인 결과 그 지적이 맞았다 — 다만 24는 예외였다. | ||
|
|
||
| 추정하지 않고 릴리스 라인별 소스를 직접 대조했다: | ||
|
|
||
| ``` | ||
| $ gh api "repos/nodejs/node/contents/lib/internal/test_runner/runner.js?ref=<branch>" \ | ||
| --jq .content | base64 -d | grep -c '>>> 0) + kSerializedSizeHeader' | ||
| v26.x=1 v24.x=0 v22.x=0 v20.x=0 v18.x=0 | ||
| v26.x-staging=1 v24.x-staging=1 v22.x-staging=0 | ||
| ``` | ||
|
|
||
| | 라인 | 취약 코드 | 수정 | 최신 릴리스 | 결론 | | ||
| |---|---|---|---|---| | ||
| | v18.x | 있음 | 없음 | — | **영구 잔존** (EOL) | | ||
| | v20.x | 있음 | 없음 | — | **영구 잔존** (EOL) | | ||
| | v22.x | 있음 | **없음** (staging도) | 22.23.2 (2026-07-29) | **잔존** — 활성 LTS인데 백포트 없음 | | ||
| | v24.x | 있음 | **staging에 있음** | 24.19.0 (2026-08-03, 취약) | **24.20.0부터 해소** | | ||
| | v26.x | 있음 | 있음 | 26.7.0 (2026-08-05) | 해소됨 | | ||
|
|
||
| 확정 근거: | ||
| - **v24 백포트** — cherry-pick `9ed7146851`이 `v24.x-staging`에 존재. 열려 있는 릴리스 PR | ||
| **nodejs/node#65461 "2026-08-26, Version 24.20.0 'Krypton' (LTS)"** 의 CHANGELOG에 | ||
| `test_runner: convert to uint during deserialization (Aviv Keller) [#64706]` 로 명시. | ||
| - **v22 미백포트** — `v22.x-staging`의 해당 줄은 여전히 `const fullMessageSize = (` 이고 | ||
| 괄호 보정(`>>> 0`)이 없다. 백포트 PR도 없다. | ||
|
|
||
| **교훈: EOL과 백포트 도달 여부는 독립된 축이다.** "활성 LTS면 최신 수정이 와 있다"는 | ||
| 가정이 틀렸다 — v22가 반례다. 런타임을 고를 때 두 축을 따로 확인해야 한다. | ||
| 이 오류는 "수정이 X 버전에 들어갔다"에서 "그 이하 활성 버전에도 있겠지"로 건너뛴 데서 나왔다. | ||
|
|
||
| 실무적 함의: | ||
| - `[22, 24]`를 고르면 **22 job에 flake가 그대로 남는다** — 가장 자연스러워 보이는 선택이 함정이다. | ||
| - `[24]`는 `actions/setup-node`가 최신 24.x를 집으므로 **2026-08-26 이후 자동으로** 해소된다. | ||
| 그 전에 검증하면 24.19.0(취약)이라 잘못된 결론이 나온다. | ||
|
|
||
| ### 2회차 관측 — PR #44 머지 직후 main이 이 flake로 빨개졌다 | ||
|
|
||
| 2026-08-25, PR #44가 main에 머지된 직후 `test (18)`이 실패했다. annotation이 즉시 원인을 알려줬다: | ||
|
|
||
| ``` | ||
| not ok 38 - tests/task-templates.test.mjs | ||
| error: 'Unable to deserialize cloned data due to invalid or unsupported version.' | ||
| ``` | ||
|
|
||
| 같은 run의 `test (20)`은 통과했고 perf 수치도 정상이었다 | ||
| (`cold 1.70x / checkpoint 1.76x`, 예산 3x/5x). 즉 **#44의 회귀가 아니라 이 task가 다루는 | ||
| 바로 그 node 18 flake**이며, 이번에는 **main을 빨갛게 만들었다.** | ||
|
|
||
| 두 가지가 확인된다: | ||
| 1. 계측이 제 값을 했다 — 이전이라면 "exit code 1"만 보였을 실패가 **한 번에** 특정됐다. | ||
| 2. 이 flake는 산발적 불편이 아니라 **기본 브랜치를 막는다.** 관측 2회차이고 | ||
| `[24]` 이동(이 PR)이 그대로 해소책이다 — node 18 job이 사라지기 때문이다. | ||
|
|
||
| ### 결정 및 구현 (2026-08-25) | ||
|
|
||
| **결정: `engines: ">=24"` + CI matrix `[24]`** — spec 후보표의 (A2). 하위 호환 파기를 수용하고 | ||
| EOL 탈출과 flake 해소를 동시에 얻는 선택이다. **22를 넣지 않는 것이 핵심**이다 — | ||
| "활성 LTS 두 개"로 보이는 `[22, 24]`는 22 job에 flake를 남긴다. | ||
|
|
||
| 변경 지점 (grep 전수 확인): | ||
|
|
||
| | 파일 | 전 | 후 | | ||
| |---|---|---| | ||
| | `package.json:20` | `"node": ">=18"` | `">=24"` | | ||
| | `.github/workflows/test.yml` | `node: [18, 20]` | `node: [24]` (+ 사유 주석) | | ||
| | `.github/workflows/release.yml` | `node-version: 20` | `24` (+ 사유 주석) | | ||
| | `README.md` | "Node.js ≥ 18" ×2 | ≥ 24 | | ||
| | `docs/prerequisites.md` | "Node.js ≥ 18" ×3 | ≥ 24 | | ||
| | `CHANGELOG.md` | — | `[Unreleased] ### Changed` BREAKING 항목 | | ||
|
|
||
| 종결된 task의 spec(`docs/chad/prerequisites-doc/`)에 남은 "≥ 18" 서술은 **건드리지 않았다** — | ||
| 그 시점 사실의 historical record이고 다른 task의 SSOT다. | ||
|
|
||
| ### 검증 | ||
|
|
||
| `mise x node@24 -- npm test` → **415 tests, 0 fail** (1 skipped, 기존부터). | ||
|
|
||
| ### 주의 1 — 이 변경만으로는 아직 flake가 사라지지 않는다 | ||
|
|
||
| `node-version: 24`는 최신 24.x로 해석되는데, 2026-08-25 현재 최신은 **24.19.0이고 백포트가 | ||
| 아직 없다.** 수정이 들어간 **24.20.0은 2026-08-26 릴리스 예정**이다. | ||
| 따라서 오늘 CI가 green이어도 그것은 flake가 고쳐졌다는 증거가 아니다 — | ||
| 확률적 flake이므로 통과 1회는 원래 증거가 되지 못한다. **2026-08-26 이후에 판단할 것.** | ||
|
|
||
| ### 실수 1건 — 런타임을 바꾸자 계측이 조용히 죽었다 | ||
|
|
||
| matrix를 `[24]`로 바꾼 첫 CI 실행은 green이었지만 **annotation이 비어 있었다.** | ||
|
|
||
| 원인: PR #44에서 넣은 계측이 `grep -E '^# spawn floor'` 로 **TAP 리포터 접두사에 앵커**돼 | ||
| 있었다. node ≤20은 기본이 TAP(`# diagnostic`, `not ok 1 - name`, ` error: '...'`)이지만 | ||
| **node ≥22는 기본이 spec**(`ℹ diagnostic`, `✖ name`, `AssertionError [ERR_ASSERTION]: ...`)이다. | ||
| 런타임을 옮기는 순간 패턴이 어긋나 계측이 **실패 없이 조용히** 아무것도 내보내지 않았다. | ||
|
|
||
| 조치: 접두사가 아니라 **내용으로** 매칭하도록 바꾸고, 실패 패턴에 두 리포터를 모두 포함시켰다. | ||
| 그리고 이번에는 추정하지 않고 **네 조합(node 20·24 × 통과·실패)의 실제 출력**을 캡처해 | ||
| 패턴을 검증했다 — 네 경우 모두에서 진단 라인과 assertion 메시지가 잡히는 것을 확인했다. | ||
|
|
||
| 교훈: **런타임 버전을 바꾸면 도구의 기본 출력 형식도 함께 바뀔 수 있다.** | ||
| 출력 형식에 의존하는 계측은 런타임 이동 시 반드시 재검증해야 한다. 특히 이 실패는 | ||
| "green인데 조용히 아무것도 안 나온다"는 형태라 눈치채기 어렵다 — 계측이 죽으면 | ||
| 다음 flake는 다시 진단 불가 상태(로그 403)로 돌아간다. | ||
|
|
||
| ### 주의 2 — perf 가드의 민감도가 node 24에서 낮아진다 | ||
|
|
||
| perf 테스트(PR #44)의 비율 예산은 node 18/20 실측으로 산정됐다. node 24는 **프로세스 시작 | ||
| 비용 자체가 크다**(bare spawn: node 20 ≈ 17ms vs node 24 ≈ 37ms, 같은 머신 무부하 기준). | ||
| 분모(equal-work baseline)가 시작 비용을 포함하므로 분모가 부풀고, 비율이 1.0 쪽으로 압축된다: | ||
|
|
||
| | | node 20 | node 24 | | ||
| |---|---|---| | ||
| | bare spawn | ~17ms | ~37ms | | ||
| | baseline | ~23ms | ~42ms | | ||
| | cold | ~33ms | ~56ms | | ||
| | cold/baseline | 1.43 | **1.34** | | ||
| | 3x 예산까지 허용되는 추가 비용 | +46ms | **+69ms** | | ||
|
|
||
| 즉 macOS에서는 같은 `3x` 예산이 node 24에서 더 느슨하게 작동한다. | ||
|
|
||
| **→ CI 실측 결과 이 우려는 실현되지 않았다 (해소).** 위 수치는 macOS 것이고, | ||
| CI(Linux)의 node 24 시작 비용은 macOS와 전혀 달랐다. 같은 hosted 러너에서 받은 값: | ||
|
|
||
| | CI job | spawn floor | baseline | cold | checkpoint | | ||
| |---|---|---|---|---| | ||
| | test (18) | 23.2ms | 38.6ms | **1.68x** | 1.79x | | ||
| | test (20) | 22.1ms | 35.8ms | **1.75x** | 1.89x | | ||
| | **test (24)** | 28.4ms | 38.3ms | **1.78x** | 1.83x | | ||
|
|
||
| **세 버전이 사실상 동일하다.** Linux의 node 24 시작 비용은 28.4ms로 18·20(22~23ms)과 | ||
| 비슷하며, macOS에서 본 37~40ms 같은 팽창이 없다. 따라서 분모가 부풀어 민감도가 낮아지는 | ||
| 현상은 **macOS 국한**이고 CI에는 전이되지 않는다. **예산 재산정은 불필요하다.** | ||
|
|
||
| 교훈: 로컬(macOS) 실측만으로 CI 리스크를 단정하지 말 것 — 이 건은 로컬 수치가 | ||
| 실제보다 나쁜 쪽으로 오도했다. 계측을 CI에 심어 둔 덕에 추정 대신 측정으로 닫았다. `engines`를 `>=22`로 올리는 것은 **하위 호환을 깨는** 변경이라 | ||
| 이 task 단독으로 결정할 수 없다. spec의 Ambiguity 게이트는 이 항목 때문에 | ||
| **의도적으로 미통과 상태**로 남겨 뒀다 — 남은 모호성이 기술이 아니라 정책이기 때문이다. | ||
|
|
||
| ## Reviews | ||
| *Codex/Gemini 등 리뷰 실행 시 결과(요약·발견·조치)를 날짜와 함께 남긴다. 남기지 않은 리뷰는 "안 한 것"으로 간주.* | ||
| *기계 판독용 마커를 함께 남긴다: `<!-- harness:review kind=codex scope=worktree tip=<sha|none> at=<ISO8601> -->`* | ||
|
|
||
|
|
||
| ## Learnings | ||
36 changes: 36 additions & 0 deletions
36
docs/chad/node-test-runner-flake/node-test-runner-flake-context.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| # node-test-runner-flake — Context Card | ||
| <!-- working set only; UTF-8 <= 6 KiB, nonblank lines <= 100 --> | ||
|
|
||
| ## Now | ||
| - Goal: `node:test` 업스트림 역직렬화 버그로 CI가 간헐 실패하는 것을 멈춘다. | ||
| - Current atomic step: **[결정 대기] 지원 Node 범위** — EOL 탈출과 flake 해소를 분리해 | ||
| 답을 받아야 한다. flake까지 없애려면 22는 답이 아니고 24(24.20.0 이후)/26이어야 한다. | ||
| - Stop / human-decision condition: **지금이 그 지점이다.** 하위 호환을 깨는 정책 결정이라 | ||
| 사용자 답 없이 구현에 진입하지 않는다. spec의 Ambiguity 게이트도 이 항목 때문에 미통과 상태다. | ||
|
|
||
| ## Constraints and settled decisions | ||
| - 원인 확정: `#processRawBuffer`가 payload 길이를 **부호 있는 정수**로 읽는다 (nodejs/node#64061). | ||
| 수정은 `>>> 0` 한 줄 (PR #64706), **Node 26.7.0**에 포함. | ||
| - **EOL과 백포트는 별개 축이다** (초기 판단 정정). 실측: | ||
| v18/v20 = 수정 없음·EOL이라 영구 잔존 · **v22 = 활성 LTS인데 백포트 없음(staging에도)** · | ||
| v24 = `9ed7146851` staging, 릴리스 PR #65461 → **24.20.0(2026-08-26)부터** · v26 = 26.7.0에 포함. | ||
| - 따라서 **"활성 LTS로 이동" ≠ "flake 해소"**. 22로 가면 flake가 남는다. | ||
| - 현재 matrix `[18, 20]`은 전부 지원 종료 런타임이다. | ||
| - 우리 코드 무관 확정: 실패 파일은 스폰·직렬화 없음 · 동일 코드로 통과 이력 · 재실행 통과. | ||
| - 자동 재시도(후보 E)는 **채택 금지** — 진짜 회귀까지 숨긴다. | ||
| - PR #44(perf flake)와 **독립**. 두 flake를 한 원인으로 묶지 말 것. | ||
|
hsleedevelop marked this conversation as resolved.
Resolved
|
||
|
|
||
| ## JIT retrieval map | ||
| - Narrow globs: `.github/workflows/test.yml`, `.github/workflows/release.yml`, `package.json` | ||
| - Identifiers: `engines.node`, `matrix.node`, `node-version` | ||
| - Read next: spec의 "후보" 표 — (A) 권장, (E) 금지 근거 | ||
| - Verification command: CI annotation 수집 | ||
| `gh api repos/bd-makers/team-harness/check-runs/<job_id>/annotations` | ||
|
|
||
| ## Failure capsules (max 3 unresolved) | ||
| - (none unresolved — 원인은 확정됐고 남은 것은 정책 결정이다) | ||
|
|
||
| ## Resume checklist | ||
| - 재현은 **CI에서만** 가능하다 — 이 머신에 node 18 없음(프록시가 nodejs.org 차단), docker 없음. | ||
| - CI 로그는 403이다. `gh run view --log-failed`에 시간 쓰지 말 것 — annotation API를 쓴다. | ||
| - 결정이 나면 spec의 Ambiguity 자가진단 Constraint 항목부터 갱신하고 게이트를 다시 평가한다. | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.