GitHub API 403 오류: 재시도 전에 사용량 제한과 권한 부족 구분하기
By ON Lab · Sources checked November 7, 2026
GitHub API의 403만 보고 재시도하거나 토큰을 교체하지 마세요. 사용량 제한과 접근 권한 부족 모두 403으로 나타날 수 있습니다. 응답 본문과 retry-after, x-ratelimit-remaining, x-ratelimit-reset을 함께 읽은 뒤 기다릴 문제인지 권한을 확인할 문제인지 구분해야 합니다.
이 글은 새 인증을 만들거나 권한을 넓히는 절차가 아닙니다. 이미 허용된 요청의 오류를 어떻게 기록하고 분류할지 설명합니다. 응답을 읽는 단계와 실제 권한 변경·재요청 단계는 분리하세요. 특히 반복 요청으로 오류를 확인하려 하면 원래 문제와 별개로 추가 제한을 만들 수 있습니다.
실제로 보고된 오분류 사례
연결한 공개 프로젝트 이슈는 모든 403을 rate limit으로 묶던 코드 때문에, 저장소 접근이나 설치 권한 문제가 사용량 부족처럼 보이는 현상을 설명합니다. 이슈 작성자는 다른 앱으로 순환 재시도하면 불필요한 요청만 늘어날 수 있다고 지적했습니다. 이 사례는 특정 프로젝트의 오류 분류 문제이며 GitHub 전체 장애를 뜻하지 않습니다.
현재 GitHub 공식 문서도 일차·이차 사용량 제한에서 403 또는 429를 받을 수 있다고 설명합니다. 한편 Resource not accessible by integration 또는 personal access token 관련 접근 불가 메시지는 별도의 권한 확인 경로로 안내합니다. 상태 코드 하나로 두 상황을 합치면 기다려도 해결되지 않는 문제를 놓치게 됩니다.
응답을 한 번 저장하고 필요한 항목만 읽기
이미 받은 응답이 있다면 그것을 먼저 재사용하세요. 아래는 인증이 필요 없는 공개 API를 읽는 형식의 예시이며, 현재 문제가 있는 비공개 요청을 대신 재현하지 않습니다. 운영 환경의 실제 토큰을 명령 이력이나 공개 보고서에 넣지 마세요.
curl -sS -D response-headers.txt -o response-body.txt -H "User-Agent: your-app-name" https://api.github.com/zen
이 주소는 응답이 반드시 JSON이라는 예제가 아닙니다. 저장 파일 확장자로 본문 형식을 확정하지 말고 Content-Type과 실제 내용을 확인하세요. 자신의 허용된 엔드포인트에서 이미 실패한 응답이 있다면 상태, 필요한 헤더, 비밀정보를 지운 메시지만 남기는 편이 더 정확합니다. 이 글에서는 실제 제한을 유발하는 요청을 보내지 않았습니다.
기다릴 조건과 기다리는 방법
| 관측 | 판단 | 다음 행동 |
|---|---|---|
| 사용량 제한 오류와 Retry-After 존재 | 서버가 대기 초를 안내 | 해당 초가 지난 뒤까지 재요청하지 않기 |
| 잔여량 0 | 일차 한도 소진 신호 | reset의 UTC epoch 초가 지난 뒤 확인 |
| 이차 제한 메시지, 위 안내 없음 | 추가 제한의 대기 경로 | 최소 1분 기다리고 반복 실패 시 간격을 늘리되 재시도 상한 설정 |
| 접근 불가 메시지 | 권한 검토 필요 | 같은 요청의 반복 대신 엔드포인트·토큰 종류·설치 범위 확인 |
| 설명 없는 403 | 원인 미확인 | 사용량 부족이라고 단정하지 않고 전체 오류 문맥 확인 |
위 대기 순서는 사용량 제한 오류라는 근거가 있을 때 적용하는 공식 지침입니다. Retry-After가 초 단위라는 점과 reset이 UTC epoch 초라는 점을 혼동하지 마세요. reset 숫자를 그대로 “몇 초 기다리기”에 넣으면 잘못된 대기 시간이 됩니다.
대기 후에도 계속 실패하면 무한 루프가 아니라 제한된 재시도와 실패 종료가 필요합니다. 공식 문서는 제한 상태에서 계속 요청하면 통합이 차단될 수 있다고 안내합니다. 앱·IP·계정을 바꾸어 제한을 피하는 방식은 이 글의 해결책이 아닙니다.
잔여량이 남아 있어도 이차 제한일 수 있습니다
잔여량은 모든 제한의 공통 성공 신호가 아닙니다. 일차 한도의 잔여량이 남아 있더라도 이차 제한 메시지를 받았다면 그 오류를 읽어야 합니다. 반대로 잔여량이 남아 있다는 이유로 권한 오류를 이차 제한으로 자동 분류할 수도 없습니다.
상태·본문·헤더를 묶어 한 요청의 증거로 보존하세요. 다른 시각에 보낸 rate-limit 조회의 잔여량을 이전 실패 응답의 헤더처럼 사용하면 안 됩니다. 요청 시각, 엔드포인트, 응답 시각을 연결해야 서로 다른 한도나 변경된 상태를 혼동하지 않습니다.
권한 문제에서는 무엇을 확인하나요?
- 오류를 받은 정확한 엔드포인트와 HTTP 메서드를 확인합니다.
- 이미 승인된 토큰의 종류가 GitHub App 설치 토큰인지, fine-grained 토큰인지 등 실제 종류를 확인합니다.
- 그 엔드포인트의 공식 권한 표와 현재 허용된 저장소 범위를 대조합니다.
- 응답에
X-Accepted-GitHub-Permissions가 있으면 필요한 권한 조합의 단서로 읽습니다. - 권한을 바꿔야 한다면 해당 소유자의 정식 승인 경로를 따릅니다. 새 토큰을 무작정 만들지 않습니다.
공식 문서에서 쉼표로 연결된 권한은 함께 필요한 조합이며, 세미콜론으로 나뉘는 경우 여러 대안 조합일 수 있습니다. 헤더는 지금 가진 권한이 충분하다는 증명도, 자동으로 새 권한을 부여해도 된다는 승인도 아닙니다. 엔드포인트 문서를 최종 기준으로 읽으세요.
404와 User-Agent 문제를 따로 확인하기
비공개 리소스는 올바르게 인증되지 않았을 때 존재 여부를 숨기기 위해 404가 반환될 수 있습니다. 따라서 404를 항상 삭제된 저장소라고 기록하면 안 됩니다. 동시에 다른 사용자의 비공개 저장소가 존재한다고 추측해 반복 조회하지 마세요.
공식 문서는 유효한 User-Agent가 없는 요청도 거절된다고 설명합니다. curl은 기본 User-Agent를 보내지만 직접 만든 HTTP 클라이언트는 다를 수 있습니다. 이 문제 역시 사용량 제한으로 묶지 말고 요청 형식부터 확인합니다.
팀에 전달할 최소 진단 기록
오류 시각과 시간대, 상태 코드, 정확한 메서드·엔드포인트 형태, 비밀을 지운 본문 메시지, 해당 응답의 대기·잔여량·reset·권한 헤더를 남기세요. 토큰 값, 전체 Authorization 헤더, 비공개 저장소의 불필요한 세부 내용은 제외합니다. 실제로 기다린 시간과 다음 요청의 결과는 별도 행으로 기록합니다.
완료 기준은 로그에서 경고가 사라지는 것이 아니라 원래 허용된 요청이 의도한 범위에서 성공하고 결과가 맞는지 확인하는 것입니다. 이 글은 공식 분류 절차와 공개 독자 사례를 검수했습니다. 실제 토큰 권한·이차 제한 발생·대기 후 복구를 재현한 결과는 아닙니다.
저장소 라이선스 확인이 원래 작업이라면 배지와 파일·API 근거를 구분하는 안내를 참고하세요. API 요청 성공도 라이선스 적용 범위를 자동으로 확정하지 않습니다.