プライベートリポジトリで git fetch origin が 403 になる — URL埋め込みPATの落とし穴と対策

プライベートリポジトリで git fetch origin が 403 になる — URL埋め込みPATの落とし穴と対策

GitHubプライベートリポジトリでgit pushは成功するのにgit fetch originが403になる問題の原因と対策。PATをURLに埋め込むpush方式でoriginの認証が非対称になる落とし穴と、$AUTHED_ORIGINパターンでの解決策を紹介します。
2026.07.11

はじめに

GitHub の Organization プライベートリポジトリで CI/CD スクリプトやリリース自動化を組んでいると、こんなエラーに遭遇することがあります。

remote: Write access to repository not granted.
fatal: unable to access 'https://github.com/YOUR-ORG/your-repo.git/': The requested URL returned error: 403

git push は成功するのに、直後の git fetch origin で 403。一見矛盾したこの挙動、原因は PAT(Personal Access Token)を URL に埋め込む push 方式 と、認証情報を持たない origin リモート の組み合わせにありました。

この記事では、なぜこの問題が起きるのか、そしてプライベートリポジトリで安全に git 操作を行うための $AUTHED_ORIGIN パターンを紹介します。

前提環境

  • GitHub Organization のプライベートリポジトリ
  • PAT は 1Password で管理(git config にトークンを保存しない運用)
  • gh CLI の Organization 用自作ラッパー関数(gh-org)でトークンを取得(後述)
  • macOS / zsh

何が起きているのか

典型的な push スクリプト

Organization リポジトリへの push では、git push origin main が認証エラーで失敗するため、PAT を URL に埋め込んで push する方式を採用していました。

source ~/.zshrc
GH_TOKEN=$(gh-org auth token)
AUTHED_ORIGIN=$(git remote get-url origin | sed "s|https://|https://YOUR_USERNAME:${GH_TOKEN}@|")
git push "$AUTHED_ORIGIN" main

YOUR_USERNAME は自分の GitHub ユーザー名に置き換えてください。この方式自体は問題なく動きます。しかし、URL ベースの push には ローカルのトラッキング ref が更新されない という副作用があります。push 後に git status を実行すると、push が成功しているのに "Your branch is ahead of 'origin/main' by N commits" と表示されてしまいます。

fetch で ref を更新しようとして 403

この副作用を解消するために、push 直後に git fetch を実行していました。

git push "$AUTHED_ORIGIN" main && git fetch origin  # ← ここで 403

git push$AUTHED_ORIGIN(トークン入り URL)を使うので成功。しかし git fetch origin は git config に登録されたリモート URL をそのまま使います。

# origin のリモート URL(トークンなし)
https://github.com/YOUR-ORG/your-repo.git

パブリックリポジトリなら認証なしでも fetch できますが、プライベートリポジトリでは読み取りにも認証が必要。結果、403 が返ります。

問題の図解

github-private-repo-git-fetch-403-authed-origin-auth-mismatch

解決策: すべての git 操作で $AUTHED_ORIGIN を使う

修正はシンプルです。origin を使っていた箇所をすべて $AUTHED_ORIGIN に統一します。

source ~/.zshrc
GH_TOKEN=$(gh-org auth token)
AUTHED_ORIGIN=$(git remote get-url origin | sed "s|https://|https://YOUR_USERNAME:${GH_TOKEN}@|")

# push も fetch も同じ認証済み URL を使う
git push "$AUTHED_ORIGIN" main && git fetch "$AUTHED_ORIGIN"

pull / fetch --prune も同様

リリーススクリプトなどで pull や prune を実行する場合も同じです。

# NG: origin はトークンなし → 403
git pull origin main && git fetch --prune origin

# OK: 認証済み URL を一貫して使用
git pull "$AUTHED_ORIGIN" main && git fetch --prune "$AUTHED_ORIGIN"

なぜ origin にトークンを設定しないのか

git remote set-url origin でトークン入り URL を設定すればいいのでは?」と思うかもしれません。しかし、以下の理由からこの方法は避けています。

方式 メリット デメリット
git remote set-url でトークン埋め込み 以降の操作で origin がそのまま使える トークンが .git/config に平文で残る
環境変数 $AUTHED_ORIGIN トークンがファイルに残らない すべての操作で $AUTHED_ORIGIN を明示する必要がある
git credential helper origin がそのまま使える、トークンもキーチェーンで管理 Organization PAT と個人 PAT の使い分けが煩雑

1Password で管理している Organization PAT を毎回取得して使い捨てる $AUTHED_ORIGIN 方式は、セキュリティ面では最も安全です。トレードオフとして、スクリプト内の origin$AUTHED_ORIGIN に統一する手間が必要になります。

&& チェーンの罠

もうひとつ気づきにくい問題があります。git fetch origin が 403 で失敗すると、&& チェーンの後続コマンドも実行されません。

# fetch が 403 で失敗 → tags の push が実行されない
git push "$AUTHED_ORIGIN" main && git fetch origin && git push "$AUTHED_ORIGIN" --tags

実際にこの問題に遭遇したとき、「push は成功したのにタグが push されていない」という状況になりました。エラーメッセージは git fetch origin の 403 を報告していましたが、タグが push されていないことには気づきにくい。

対策として、タグの push は fetch と分離するか、fetch を修正して $AUTHED_ORIGIN を使うようにします。

# fetch を修正(推奨)
git push "$AUTHED_ORIGIN" main && git fetch "$AUTHED_ORIGIN"
git push "$AUTHED_ORIGIN" --tags

# または、fetch の失敗を許容する
git push "$AUTHED_ORIGIN" main
git fetch "$AUTHED_ORIGIN" || true
git push "$AUTHED_ORIGIN" --tags

gh CLI ラッパーのトークン取得パターン

参考までに、1Password から Organization PAT を取得する gh-org ラッパーのパターンを紹介します。

~/.zshrc
# 1Password 管理の Organization PAT で gh を実行するラッパー
gh-org() {
  GH_TOKEN=$(op read "op://Private/GitHub Org PAT/token") gh "$@"
}
# トークンの取得
GH_TOKEN=$(gh-org auth token)

# 認証済み URL の構築
AUTHED_ORIGIN=$(git remote get-url origin | sed "s|https://|https://YOUR_USERNAME:${GH_TOKEN}@|")

gh-org auth tokenGH_TOKEN 環境変数がセットされた状態で gh auth token を実行するため、1Password から取得した PAT がそのまま返されます。

まとめ

状況 原因 対策
git fetch origin が 403 プライベートリポジトリで認証なし fetch $AUTHED_ORIGIN を使う
push 後の git status が "ahead" URL ベース push はトラッキング ref を更新しない push 後に git fetch "$AUTHED_ORIGIN"
&& チェーンで後続が実行されない 途中の fetch 失敗でチェーンが中断 fetch を修正するか、分離する

ポイントは 「プライベートリポジトリでは読み取りにも認証が必要」 という当たり前の事実です。git push だけ認証済み URL を使い、git fetch は素の origin を使うという非対称な構成が、この問題の根本原因でした。

PAT を URL に埋め込む方式を採用する場合は、push / fetch / pull すべてで一貫して $AUTHED_ORIGIN を使うことを徹底しましょう。

参考

この記事をシェアする

関連記事