GitHub CLI v2.103.0이 몇 시간 안에 만료되는 자동 갱신 토큰을 옵트인으로 열었다. GH_TOKEN에 옮긴 사본은 갱신되지 않고, gh 자신도 저장본 대신 그 사본을 쓴다. 옵트인 전에 셸 프로필과 에이전트·MCP 설정에 토큰을 복사해 둔 곳을 찾는다.
코딩 에이전트에게 PR을 열게 할 때 GitHub 토큰을 넘기는 손쉬운 방법이 두 가지 있다. 셸 프로필에 export GH_TOKEN=$(gh auth token)을 넣어 두거나, MCP 서버를 등록할 때 토큰 값을 환경변수로 넘긴다. 터미널의 gh와 에이전트 프로세스가 같은 토큰을 쓰게 하려는 선택이다.
지금까지는 이 복사가 문제를 만들지 않았다. gh가 받아 오던 OAuth 토큰에 만료가 없어서 원본과 사본의 수명이 같았다. GitHub의 10월 21일 GitHub CLI 공지는 이 전제를 바꾼다. 옵트인하면 원본은 몇 시간 안에 만료되고 gh가 새로 받아 오지만, 사본은 처음 값 그대로 남는다.
GH_TOKEN 한 줄이 옵트인 효과를 지운다
v2.103.0부터 gh auth login --expiring으로 로그인 한 번에, gh config set expiring_credentials enabled로 모든 로그인에 옵트인한다. 그러면 gh는 수명이 짧은 액세스 토큰과 회전하는 리프레시 토큰을 저장하고, 필요할 때 액세스 토큰을 새로 받는다. GitHub는 유출된 액세스 토큰이 "몇 시간 안에" 쓸 수 없게 된다고 적었고, 정확한 수명은 밝히지 않았다.
GitHub CLI v2.103.0 릴리스 노트(10월 21일)는 환경변수 쪽을 따로 적었다. GH_TOKEN이나 GITHUB_TOKEN으로 넘긴 토큰은 절대 갱신하지 않는다. 그리고 GitHub CLI 환경변수 매뉴얼(현행)은 이 변수가 저장된 자격 증명보다 우선한다고 정해 두었다.
두 규칙을 겹치면 결과가 하나로 정해진다. 셸 프로필이 GH_TOKEN을 채우는 순간, gh는 갱신할 수 있는 저장 자격 증명을 두고 변수 속 사본을 쓴다. 사본이 만료되면 gh 자신도 인증에 실패한다.
| 토큰을 쓰는 경로 | 갱신 여부 |
|---|---|
gh 명령이 저장된 자격 증명을 직접 읽음 | 갱신됨 |
gh auth setup-git으로 gh를 git 자격 증명 도우미로 지정 | 갱신됨 (git 2.46 이상 권장) |
토큰이 필요할 때마다 gh auth token을 새로 호출 | 갱신됨 |
GH_TOKEN·GITHUB_TOKEN에 넣은 값 | 갱신 안 됨 |
| 한 번 읽어 파일이나 다른 도구 설정에 저장한 값 | 갱신 안 됨 |
에이전트 설정에 넣은 토큰은 등록한 순간의 값으로 고정된다
github-mcp-server README(현행)의 Claude Code 예시는 claude mcp add github -e GITHUB_PERSONAL_ACCESS_TOKEN=$GITHUB_PAT 형태다. 셸은 명령을 실행하기 전에 변수를 펼치므로, 설정 파일에는 그 순간의 토큰 문자열이 저장된다. README는 직접 만든 PAT를 쓰지만, 이 값을 $(gh auth token)으로 채운 팀이라면 옵트인 몇 시간 뒤 MCP 서버의 GitHub 호출이 실패한다.
GitHub도 오래 유지되는 토큰이 필요한 워크플로는 아직 옵트인하지 말라고 적었다. 서버가 갱신형 자격 증명을 지원하지 않으면 gh가 만료 없는 토큰으로 돌아간다는 점도 공지에 있다. 그래서 옵트인 여부는 설정값이 아니라 gh auth status에 찍힌 만료 시각으로 판정한다.
얻는 것도 분명하다. GitHub의 10월 23일 공개 모니터링 업데이트는 공개 저장소에서 찾은 유출 토큰에 Active·Inactive·Unknown 상태를 붙이지만, 알림을 닫아도 토큰은 폐기되지 않는다고 적었다. 지금은 Active 토큰을 사람이 찾아 폐기한다. 갱신형 액세스 토큰은 유출 뒤 몇 시간이면 스스로 쓸 수 없게 된다. 토큰별 만료일·범위를 추리는 방법은 GitHub 자격 증명 인벤토리 CSV가 나왔다, 에이전트에 물린 토큰은 만료일·범위·마지막 사용일로 추린다에서 다뤘다.
가져갈 것
옵트인 전에 아래 순서로 돌린다. grep은 토큰이 화면에 찍히지 않도록 파일 이름만 출력한다.
# 1. 2.103.0 미만이면 --expiring 플래그가 없다
gh --version
# 2. 토큰을 변수·파일로 옮기는 셸 설정과 스크립트
grep -rlE 'gh auth token|GH_TOKEN|GITHUB_TOKEN' ~/.zshrc ~/.bashrc ~/.profile ./scripts 2>/dev/null
# 3. 에이전트·MCP 설정에 문자열로 저장된 GitHub 토큰(ghp_·gho_·ghu_·github_pat_)
grep -lE 'gh[opu]_[A-Za-z0-9]{20,}|github_pat_' ~/.claude.json .mcp.json ~/.codex/config.toml 2>/dev/null
# 4. git HTTPS도 gh가 갱신하게 한다 (git 2.46 이상 권장)
git --version
gh auth setup-git
# 5. 2~3에서 나온 파일을 고친 뒤 옵트인하고 만료 시각이 찍히는지 확인
gh auth login --expiring
gh auth status
안 되는 경우
- 옵트인하지 않은 조직은 아직 바뀌는 것이 없다. 기존 로그인과 직접 넣은 토큰은 그대로 동작하고, 신규 로그인 기본값 전환은 날짜가 정해지지 않았다.
- GitHub Enterprise Server 3.23 미만에서는 옵트인해도 만료 없는 토큰으로 돌아간다.
- 토큰 하나를 오래 들고 도는 배치 작업은 GitHub 권고대로 옵트인 대상에서 뺀다.
- 소스가 밝히지 않은 것: 액세스 토큰의 정확한 수명, 갱신 시점, 리프레시 토큰의 수명과 유출 시 처리 방법.
이번 주에 바꿀 것은 셸 프로필의 export GH_TOKEN=$(gh auth token) 한 줄이다. 이 줄을 지우고, 토큰이 필요한 스크립트는 쓰는 순간마다 gh auth token을 다시 부르게 고친다. 기본값이 바뀐 뒤 새로 로그인한 노트북에서 에이전트가 원인 모를 인증 오류를 내기 전에 끝내는 편이 싸다. 에이전트에 어떤 자격 증명을 어디서 물릴지는 도입 설계 단계에서 정할 항목이다. 사업영역 →