토큰을 발급하려는데 Repository access 목록에 대상 저장소가 안 보이고, 억지로 만든 토큰으로 git ls-remote 를 하면 403 이 난다.
remote: Write access to repository not granted.
fatal: unable to access ... : The requested URL returned error: 403
읽기만 하는 ls-remote 인데 write 를 말하니 헷갈리지만, 이 메시지의 실제 의미는 "이 토큰은 그 저장소에 대한 권한 자체가 없다" 이다. GitHub 은 read 든 write 든 가리지 않고 같은 문구를 돌려준다. 발급 화면에 저장소가 안 보였다는 것은 애초에 그 저장소 권한을 줄 수 없었다는 뜻이므로, 두 증상은 하나의 문제다.
1. Resource owner 미스매치. fine-grained PAT 은 발급할 때 Resource owner 를 하나만 고를 수 있고, 그 owner 소유의 저장소에만 동작한다. 개인 계정으로 발급했는데 저장소가 Organization 소유면 항상 403 이다. https://github.com/<OWNER>/<REPO> 의 <OWNER> 가 본인 ID 인지 Org 명인지 먼저 확인한다. 발급 화면의 Resource owner 목록에 그 Org 가 없다면 Org 가 fine-grained PAT 을 허용하지 않은 것이다.
2. 토큰의 access 목록 누락. Only select repositories 를 골랐다면 거기 명시한 저장소만 허용된다. Settings → Developer settings → Personal access tokens → Fine-grained tokens → 해당 토큰 → Repository access 를 확인한다.
3. Org 승인 대기. Org 소유 저장소는 fine-grained PAT 에 대해 Org 관리자의 명시적 승인이 필요하다. 승인 전에는 목록에도 안 뜨고 호출도 403 이다.
GitLab 이라면 결이 조금 다르다. fine-grained PAT 의 프로젝트 셀렉터가 직접 멤버로 등록된 프로젝트만 노출하는 동작이 있어, 그룹 상속만으로는 목록에 안 잡히는 경우가 흔하다. 대상 프로젝트의 Settings → Members 에서 본인이 Direct 멤버인지 보고, 아니면 Developer 이상으로 직접 추가한다. 최상위 그룹에서 Restrict personal access token creation 을 켜 뒀을 수도 있다.
git ls-remote 는 저장소의 모든 ref 를 한 번에 읽는 저장소 단위 조회라 로컬 브랜치와 무관하다. fine-grained PAT 의 Contents 권한도 저장소 단위지 브랜치별로 쪼개지지 않는다. 브랜치를 바꿔 다시 시도해 봐도 결과는 같다.
git ls-remote https://<user>:${TOKEN}@github.com/<owner>/<repo>.git
git ls-remote https://<user>:${TOKEN}@github.com/<owner>/<repo>.git refs/heads/develop
curl -H "Authorization: Bearer ${TOKEN}" https://api.github.com/repos/<owner>/<repo>
브랜치 보호 규칙은 push 에만 영향을 주고 fetch · pull · ls-remote 에는 영향이 없다. 커밋이 하나도 없는 빈 저장소는 결과가 비어 있을 뿐 403 이 나지는 않는다.
https://<USERNAME>:<TOKEN>@github.com/<OWNER>/<REPO>.git
이 형태는 동작하지만 셸 히스토리 · .git/config · 프로세스 목록에 토큰이 남는다. 테스트가 끝나면 정리한다.
read -s TOKEN
export TOKEN
git clone "https://<user>:${TOKEN}@github.com/<owner>/<repo>.git"
unset TOKEN
상시로는 credential helper 에 맡기는 편이 낫다.
git config --global credential.helper store
Contents: Read-only 토큰이라면 git push 가 403 으로 실패하는 것이 정상이다. push 까지 된다면 권한이 과하게 부여된 것이다.