새 터미널이면 먼저 변수를 불러온다.
cd ~/gh-pm-lab source pm-lab/env.sh
1gh api로 GitHub 기능 직접 호출하기
gh에는 마일스톤 전용 명령이 없다. 대신 gh api로 GitHub의 REST API를 직접 호출한다. 주소와 필드 이름은 마일스톤 API 문서에 적힌 것을 그대로 쓴다.
| 부분 | 뜻 |
|---|---|
--method POST | 새로 만든다. GET은 조회, PATCH는 수정이다. |
"repos/$PM_REPO/milestones" | 요청할 주소. 실습 저장소의 마일스톤 목록을 가리킨다. |
-f title="..." | 보낼 값. -f 하나에 필드 하나를 적는다. |
--jq '...' | 돌려받은 JSON에서 필요한 값만 꺼낸다. |
2출시 마일스톤 두 개 만들기
gh api --method POST "repos/$PM_REPO/milestones" \
-f title="$PM_MILESTONE" \
-f description="사전신청부터 운영 확인까지 비공개 베타 검증" \
-f due_on="2026-11-06T08:00:00Z" \
--jq '{number, title, due_on}'
gh api --method POST "repos/$PM_REPO/milestones" \
-f title="M2 공개 런칭" \
-f description="공개 안내와 운영 대응 준비 완료" \
-f due_on="2026-11-20T08:00:00Z" \
--jq '{number, title, due_on}'
{"due_on":"2026-11-06T08:00:00Z","number":1,"title":"M1 비공개 베타"}
{"due_on":"2026-11-20T08:00:00Z","number":2,"title":"M2 공개 런칭"}
due_on은 협정 세계시(UTC) 날짜와 시각이다. 끝의 Z가 UTC를 뜻한다. 08:00 UTC는 한국 시각 17:00이다. 2026-11-06T00:00:00Z로 적으면 한국에서는 11월 6일 09:00이지만, 미국 서부(UTC−8)에서는 11월 5일 16:00로 표시된다. 다른 시간대의 팀원이 있으면 마감 시각을 함께 정한다.
조회
gh api --method GET "repos/$PM_REPO/milestones" \
-f state=all --paginate \
--jq '.[] | {number, title, due_on, open_issues, closed_issues}'
{"closed_issues":0,"due_on":"2026-11-06T08:00:00Z","number":1,"open_issues":0,"title":"M1 비공개 베타"}
{"closed_issues":0,"due_on":"2026-11-20T08:00:00Z","number":2,"open_issues":0,"title":"M2 공개 런칭"}
open_issues와 closed_issues는 이 마일스톤에 붙은 이슈의 수다. 지금은 업무가 없으므로 0이다. 마일스톤 제목은 이슈를 등록할 때 그대로 입력하는 값이므로 이후에 바꾸지 않는다.
3라벨 설계: 무엇을 라벨로 두지 않는가
라벨은 업무의 종류(kind:), 담당 영역(area:), 예외 상황(blocked)만 표현한다. Priority와 Status는 라벨로 만들지 않는다.
Priority를 라벨과 Project 필드 두 곳에 두면 다음 상황이 생긴다. LP-03에 P0 라벨이 붙어 있고 Project의 Priority 필드는 P1이다. 둘 중 무엇이 맞는지는 기록만으로 알 수 없고, 누군가 한쪽을 고칠 때마다 다른 쪽도 고쳐야 한다. 이 과정은 Priority와 Status를 Project 필드 한 곳에서만 관리한다.
| 라벨 | 뜻 | 예 |
|---|---|---|
kind:task | 실행 업무 | LP-01 ~ LP-06 |
kind:meeting | 스프린트 운영 기록 | Sprint 1 운영 이슈 |
area:product | 정책과 기획 | LP-01 정책 확정 |
area:design | 화면과 콘텐츠 | LP-02 랜딩 페이지 |
area:engineering | 구현과 운영 | LP-03 신청 처리 |
area:qa | 검증 | LP-05 QA |
blocked | 해결을 기다리는 업무 | 정책 미확정으로 멈춘 업무 |
4라벨 만들기
gh label create "kind:task" -R "$PM_REPO" --color 1D76DB --description "실행 업무" gh label create "kind:meeting" -R "$PM_REPO" --color D4C5F9 --description "스프린트 운영 기록" gh label create "area:product" -R "$PM_REPO" --color 0E8A16 --description "정책과 기획" gh label create "area:design" -R "$PM_REPO" --color C5DEF5 --description "화면과 콘텐츠" gh label create "area:engineering" -R "$PM_REPO" --color 5319E7 --description "구현과 운영" gh label create "area:qa" -R "$PM_REPO" --color FEF2C0 --description "검증" gh label create "blocked" -R "$PM_REPO" --color B60205 --description "해결을 기다리는 업무"
✓ Label "kind:task" created in gildong/launchpad-pm-lab ✓ Label "kind:meeting" created in gildong/launchpad-pm-lab ... ✓ Label "blocked" created in gildong/launchpad-pm-lab
--color는 # 없이 쓰는 16진수 색상 코드다. 색은 팀이 바꿔도 된다.
조회
gh label list -R "$PM_REPO" --limit 100
새 저장소에는 bug, documentation, enhancement 등 기본 라벨 9개가 함께 있으므로 목록에는 16개가 보인다. 기본 라벨은 이 과정에서 쓰지 않지만 지우지 않아도 된다.
- 마일스톤 조회 결과에 M1 비공개 베타와 M2 공개 런칭 두 줄이 있다.
- M1의
due_on이2026-11-06T08:00:00Z이다. - 라벨 목록에
kind:2개,area:4개,blocked1개가 있다. - Priority와 Status를 라벨로 만들지 않는 이유를 한 문장으로 말할 수 있다.
막히면
| 증상 | 확인과 해결 |
|---|---|
Validation Failed, already_exists (마일스톤) | 같은 제목의 마일스톤이 이미 있다. 조회 명령으로 확인하고 다음 단계로 넘어간다. |
Validation Failed, due_on | 날짜 형식이 틀렸다. YYYY-MM-DDTHH:MM:SSZ 형식인지, T와 Z가 있는지 확인한다. |
label with name "kind:task" already exists | 같은 라벨이 있다. 목록을 확인하고 넘어간다. 색과 설명을 이 교안의 값으로 맞추려면 같은 명령 끝에 --force를 붙여 다시 실행한다. |
HTTP 404 | $PM_REPO가 비어 있거나 틀렸다. echo "$PM_REPO"로 확인하고 source pm-lab/env.sh를 실행한다. |
마치며...
저장소에 출시 목표 두 개가 마감일과 함께 생겼고, 업무를 종류와 영역으로 나눌 라벨 일곱 개가 생겼다. 실습 3에서는 이 마일스톤과 라벨을 붙인 첫 업무를 작성한다.