【Git】ブランチが削除できないときの原因と対処法完全ガイド|12エラー診断・worktree/protected branch/タグ衝突・reflog復旧まで

【Git】ブランチが削除できないときの原因と対処法(安全に消す手順) Git

git branch -dを叩いた瞬間に赤いエラー。「not fully merged」「checked out at」「Couldn’t find remote ref」「protected branch」——ブランチ削除で遭遇するエラーは実は12種類以上あり、表面的な対処では再発することが多い領域です。

単に-D(強制削除)で突破すると、未マージコミットが本当に必要だったケースで致命傷になります。逆に原因を知っていれば、ほとんどのエラーは3ステップ以内で安全に解決できます。

この記事では、ブランチ削除時に発生する全エラーパターンの原因診断と対処を、エラーメッセージ別の早見表診断フローで整理。worktree・protected branch・HEAD参照・upstream・submodule・タグ衝突・権限エラーなど深部の原因まで掘り下げ、さらに誤削除からの復旧手順チーム運用で削除トラブルを未然に防ぐルールまでまとめた完全版です。

ブランチの一般的な削除手順そのものは【Git】ブランチを削除する方法にまとめています。本記事は「削除しようとしたら止められた」シナリオ特化のトラブルシューティング集として使ってください。

この記事で学べること

  • ブランチ削除エラーの12パターン完全網羅と診断フロー
  • エラーメッセージ別早見表(30秒で原因特定)
  • worktree・protected branch・HEAD参照など深部の原因への対処
  • GitHub/GitLab のブランチ保護エラーの突破手順
  • リモート削除が反映されないときの対処(ローカルのstale参照掃除)
  • タグと同名ブランチ・submoduleブランチの削除方法
  • 誤削除した場合のreflog/fsck復旧手順
  • 削除トラブルを未然に防ぐチーム運用ルール
スポンサーリンク

エラーメッセージ別 30秒早見表

削除時に出る代表的なエラーメッセージから、原因と対処を一発検索できるようにしました。詳細は後続セクションで深掘りします。

エラーメッセージ 原因 対処のキー
Cannot delete branch 'X' checked out at ... 現在または別worktreeでcheckout中 別ブランチへ切替/worktree解除
The branch 'X' is not fully merged 未マージの独立コミットが存在 取り込むか-Dで強制削除
branch 'X' not found 名前のtypo/大小文字違い git branch -aで実在確認
remote ref does not existCouldn't find remote ref リモートに既に存在しない fetch --pruneでローカル掃除
remote: error: GH006: Protected branch update failed GitHub Branch protection Settings→Rules→Allow deletionsを許可
! [remote rejected] ... (refusing to delete the current branch) リモートのHEAD参照先 default branchを別に変更してから削除
fatal: not a valid ref 同名のタグが存在して曖昧 refs/heads/Xでフルパス指定
You are on a branch yet to be born 初期化直後でコミットが無い 初回commitしてから削除
remote: error: refusing to delete the current branch リモートのdefault branch GitHub/GitLabでdefault branch変更
remote: Permission to ... denied (403) 削除権限不足 Admin権限かPAT権限を確認
error: some refs could not be updated 複数要因(保護・権限・refs競合) 詳細ログ確認+本記事で特定
fatal: cannot force update the current branch submoduleで現在参照中 親のcheckout変更後に削除

診断フロー:3秒で削除できる状態か判定

削除前チェック
Q1: そのブランチは今いる場所(current branch)?
   YES → Q2
   NO  → Q3

Q2: まず別のブランチへ切り替える
   git switch main
   → Q3へ

Q3: そのブランチは worktree で開かれている?
   YES → git worktree remove <path> で解除
   NO  → Q4へ

Q4: ローカル削除 or リモート削除?
   ローカル → Q5へ
   リモート → Q7へ

Q5: main/currentブランチにマージ済み?
   YES → git branch -d <name>
   NO  → Q6へ

Q6: 本当に不要?(コミット失う覚悟がある?)
   YES → git branch -D <name>(強制)
   NO  → まずマージ or cherry-pick で救出

Q7: リモート側はBranch protectionで保護されている?
   YES → Settings→Rules→Allow deletions を許可
   NO  → git push origin --delete <name>

削除前の必須1コマンド:git log <削除対象> --not main --oneline。これで「mainに取り込まれていないコミット」が一覧で見えます。空なら安全に-d、出てくるなら本当に不要か再確認。

エラー①:Cannot delete branch checked out at …

典型的な発生シーン
$ git branch -d feature/login
error: Cannot delete branch 'feature/login' checked out at '/home/user/repo'

原因

削除対象のブランチに自分が今いるか、別のworktreeで同じブランチがcheckoutされています。Gitは「使っている最中のブランチを消す」動作を禁止しています。

対処

現在ブランチの場合
# 自分が対象ブランチにいる場合
git switch main           # 別ブランチへ移動
git branch -d feature/login
worktreeで使用中の場合
# worktree一覧を確認
git worktree list
# /home/user/repo              a1b2c3d [main]
# /home/user/repo-feature      d4e5f6c [feature/login]  ← これが原因

# 該当worktreeを削除(作業ファイルも消えるので注意)
git worktree remove /home/user/repo-feature

# その上でブランチ削除
git branch -d feature/login

worktreeを消すとそのディレクトリ配下の未コミット変更も消えます。重要な作業が残っている可能性があるので、git worktree listで確認後、必要なら対象ディレクトリに移動してcommit/stashしてから削除してください。

エラー②:The branch X is not fully merged

典型的な発生シーン
$ git branch -d feature/experiment
error: The branch 'feature/experiment' is not fully merged.
If you are sure you want to delete it, run 'git branch -D feature/experiment'.

原因

対象ブランチに現在のHEADから辿れないコミットが残っています。Gitは「消すと失われるコミットがある」と警告しているわけです。マージしたつもりでもsquashマージやrebaseで別ハッシュになった場合、Gitはマージ済みと認識しません。

対処

失うコミットを確認してから削除
# 失われるコミットを一覧(空なら実は安全)
git log feature/experiment --not $(git for-each-ref --format="%(refname)" refs/heads/) --oneline

# もっとシンプルに:mainに対して未マージ分を見る
git log feature/experiment --not main --oneline

# 結果が空 → 安全に削除可(squash/rebase経由で取り込まれている)
git branch -D feature/experiment

# 結果あり → 本当に失ってよいか判断
#   必要なら cherry-pick で救出
git checkout main
git cherry-pick a1b2c3d
git branch -D feature/experiment

Squash mergeとの関係

GitHubのSquash and mergeでPR統合した場合、元のコミットハッシュは残らないためGitは「未マージ」と判定します。これは正常な挙動で、実質マージ済みなら-Dで強制削除してOK。確認にはgit log --cherry-mark --oneline feature..mainが有効(squash元として既に統合されたコミットに=マークが付きます)。

エラー③:branch ‘X’ not found

典型的な発生シーン
$ git branch -d feature/Login
error: branch 'feature/Login' not found.

原因と対処

ほぼ100%名前のtypoか大小文字ミス。WindowsとmacOSはデフォルトで大小文字を区別しないファイルシステムですが、Gitの内部refは大小文字を区別します。

正しい名前を確認
# ローカルブランチ一覧(大小文字もそのまま)
git branch

# リモート含む全ブランチ
git branch -a

# パターン検索
git branch --list "feature/*"

# タブ補完で確実に
git branch -d feature/<Tab>

エラー④:Couldn’t find remote ref / remote ref does not exist

典型的な発生シーン
$ git push origin --delete feature/old
error: unable to delete 'feature/old': remote ref does not exist

原因

リモート側では既に削除済みなのに、ローカルにorigin/feature/oldの古い追跡参照(stale ref)が残っているパターン。多くは他の誰かがWeb UIや別PCから削除した場合に発生します。

対処

ローカルのstale refを掃除
# リモートに存在しないローカル参照を一括削除
git fetch --prune

# 以降、自動でpruneしたい場合
git config --global fetch.prune true

# タグもpruneしたい場合
git fetch --prune --prune-tags

fetch.prune=trueを設定するとgit fetchのたびに自動的にstale参照が掃除されます。複数人のチーム開発ではほぼ必須設定。

エラー⑤:Protected branch update failed (GitHub / GitLab)

典型的な発生シーン
$ git push origin --delete release/v1
remote: error: GH006: Protected branch update failed for refs/heads/release/v1.
remote: error: Cannot delete a protected branch
To github.com:org/repo.git
 ! [remote rejected] release/v1 (protected branch hook declined)

原因

GitHub/GitLabでBranch protection ruleが設定されており、保護ブランチの削除が禁止されています。設計としては正しい動作で、削除を許可する設定変更が必要です。

対処(GitHub)

Settings → Rules → Branch protection
# Webでの設定変更
1. Repository Settings を開く
2. Branches or Rules を選択
3. 該当ルールの "Edit"
4. "Allow deletions" にチェック or ルールから該当パターンを外す
5. Save changes

# もしくはgh CLIで(Admin権限必須)
gh api -X DELETE repos/:owner/:repo/branches/release%2Fv1/protection

# 削除後にルールを戻すのを忘れずに(パターン/masterや/main/を再保護)

保護ブランチを一時的に外すのは事故の温床。release系ブランチやmain/masterの削除権限を持つのはレポジトリAdminのみ、作業中はAuditログで誰が何を操作したか記録される運用が理想。可能ならブランチを削除せずアーカイブタグを切るのがベターです。

GitLabでの手順

GitLab Settings
# GitLab Protected Branches
1. Settings → Repository → Protected branches
2. 該当ブランチで "Unprotect"
3. 削除 → 再設定

エラー⑥:リモートのデフォルトブランチは削除できない

典型的な発生シーン
$ git push origin --delete main
remote: error: refusing to delete the current branch: refs/heads/main
To github.com:org/repo.git
 ! [remote rejected] main (deletion of the current branch prohibited)

原因

リモートリポジトリのHEADが指しているブランチ(default branch)は削除不可。「現在のブランチ」という概念がリモートにもあり、それを削除すると空のリポジトリになるため拒否されます。master→main移行などで旧default branchを削除したいときによく遭遇します。

対処:default branchを先に変更

GitHubでのdefault branch変更
# GitHub Web UI で変更
1. Settings → Branches
2. Default branch を main など別のブランチに変更
3. 警告を読んで確認
4. その後 git push origin --delete master
gh CLIでdefault branchを変更
# 現在のdefault branch確認
gh repo view --json defaultBranchRef -q .defaultBranchRef.name

# 変更
gh repo edit --default-branch main

# 旧default branchを削除
git push origin --delete master

master→main移行時の完全フロー

mainブランチ作成(masterから)→リモートにpush→GitHub/GitLabでdefault branchをmainに変更→masterをリモート削除→各メンバーがgit fetch --prunegit branch -m master mainでローカル同期。詳細手順は【Git】ブランチ名を変更する方法のmaster→main移行セクションで解説しています。

エラー⑦:タグと同名ブランチでreferenceが曖昧

典型的な発生シーン
$ git branch -d v1.0
warning: refname 'v1.0' is ambiguous.
error: branch 'v1.0' not found.

原因

同じ名前のタグとブランチが両方存在。Gitが「どちらを消すべきか」判断できず曖昧(ambiguous)として処理。

対処:フルパス指定

refs/heads/を明示
# どちらが存在するか確認
git show-ref v1.0
# refs/heads/v1.0    ← ブランチ
# refs/tags/v1.0     ← タグ

# ブランチだけ削除するには refs/heads/ を使う
git update-ref -d refs/heads/v1.0

# タグだけ削除するなら
git tag -d v1.0

タグとブランチの同名衝突は運用ルールで未然に防ぐのが本筋。Gitタグはv1.0.0形式(SemVer)、ブランチはrelease/v1.0形式、のように命名体系を分けると混乱しません。詳細は【Git】タグ完全ガイドを参照。

エラー⑧:submodule由来のbranch削除不可

親リポジトリの.gitmodulesで参照中のブランチを削除するとsubmoduleが壊れるため、事前に参照を切り替える必要があります。

submodule参照の切替→削除
# 現在のsubmodule参照を確認
git config -f .gitmodules --get submodule.libs/utils.branch
# feature/old

# 参照ブランチを変更(例:main)
git config -f .gitmodules submodule.libs/utils.branch main
git submodule sync
git add .gitmodules
git commit -m "chore: bump submodule to main"

# その上でsubmodule側のブランチを削除
cd libs/utils
git push origin --delete feature/old
git branch -d feature/old

submoduleのブランチを消すと、そのsubmoduleを参照する他のリポジトリ/ブランチのビルドが壊れる可能性があります。削除前にgit grep "feature/old"でモノレポ内の参照を全件確認を推奨。

エラー⑨:You are on a branch yet to be born

典型的な発生シーン
$ git init
$ git branch -d master
fatal: You are on a branch yet to be born

原因と対処

コミットが1つも無い初期状態では、ブランチは「まだ生まれていない」扱い。削除したい場合は.git/refs/heads/<name>を直接消すか、先に1コミット作って正規ルートで削除します。

対処フロー
# 方法A:初回コミットを作って正規削除
git commit --allow-empty -m "Initial commit"
git switch -c main
git branch -D master

# 方法B:参照ファイルを直接削除(低レベル)
rm .git/refs/heads/master
# HEADファイルも確認
cat .git/HEAD
# ref: refs/heads/master ← masterを指している場合
echo "ref: refs/heads/main" > .git/HEAD

エラー⑩:Permission denied (403) — 権限不足

典型的な発生シーン
$ git push origin --delete release/v1
remote: Permission to org/repo.git denied to username.
fatal: unable to access ...: The requested URL returned error: 403

原因と対処

GitHub/GitLab側で該当ブランチの削除権限が無い。PAT(Personal Access Token)のスコープ不足、もしくはリポジトリのAdmin権限が無いケース。

確認と対処
# 認証情報の確認
gh auth status
# 現在どのユーザー・スコープでログインしているか確認

# PATのscope: repo, delete_repo 等を見直す
# → 新しいPAT発行: https://github.com/settings/tokens

# SSHキーで接続している場合
ssh -T git@github.com
# → 接続できるユーザーが正しいか確認

ロール別の削除権限(GitHub組織リポジトリ)

  • Admin:任意のブランチ削除可(保護ブランチ含む、設定による)
  • Maintain:一般ブランチは削除可、保護ブランチは不可
  • Write:一般ブランチの削除可(推奨ロール)
  • Triage以下:削除不可

エラー⑪:リモート削除したのにブランチが見える

典型的な現象
# 他の開発者がリモートのbranch/Xを削除済み
# しかし自分のローカルでは:
$ git branch -r | grep X
origin/feature/X    ← まだ見える!

原因と対処

ローカルに残ったリモート追跡参照(remote-tracking branch)のキャッシュが古いまま。git fetch単体では更新されるがstale(古い)参照は自動削除されないのがデフォルト動作です。

stale参照を削除
# 一回だけprune
git fetch --prune

# 特定リモートだけ
git fetch origin --prune

# 以降自動化(全remote共通)
git config --global fetch.prune true

# 個別のstale参照を強制削除
git branch -dr origin/feature/X

関連する「リモートブランチ確認」の完全ガイドは【Git】リモートブランチを確認する方法を参照。branch -rls-remotefetch --pruneの使い分けが詳しく解説されています。

エラー⑫:error: some refs could not be updated

典型的な発生シーン
$ git push origin --delete feature/old
remote: error: refusing to delete ...
To github.com:org/repo.git
 ! [remote rejected] feature/old (refusing to update checked out branch)
error: failed to push some refs to 'github.com:org/repo.git'

原因

複数要因の可能性(保護ブランチ・権限・参照競合・pre-receive hook)。上位に詳細メッセージがあるのでremote:行を全て読むのが原因特定の第一歩。

詳細ログ取得
# 詳細出力(GIT_TRACE)
GIT_TRACE=1 GIT_CURL_VERBOSE=1 git push origin --delete feature/old 2>&1 | less

# pre-receive hookに阻まれている場合
# → サーバー管理者(GitHubの場合はorg admin)に確認

# 保護ブランチ以外の原因候補:
# - CODEOWNERSで所有者レビュー必須
# - Requires signed commits が有効
# - Rulesetで削除禁止

削除後の掃除:stale参照・reflog・GC

削除したブランチのゴミ参照孤児コミットを放置すると、リポジトリサイズが膨らみ、clone時間が伸びます。定期メンテナンスで健全に保ちましょう。

掃除コマンド
# リモート追跡参照のstale掃除
git fetch --prune --prune-tags

# 古いreflogエントリを強制期限切れ(90日→30日など)
git reflog expire --expire=30.days --all
git reflog expire --expire-unreachable=7.days --all

# 孤児オブジェクトをGC(通常は自動実行されるが手動も可)
git gc --prune=now

# マージ済みローカルブランチを一括削除
git branch --merged main | grep -vE "^\*|main$|develop$" | xargs -n 1 git branch -d

git gc --prune=nowreflog expireを組み合わせると、誤削除ブランチのコミットも完全に消えて復旧不可能になります。「消した後に気づく」ケースを考慮し、個人環境ではreflog保持期間はデフォルト(90日)のままがおすすめ。

誤削除したブランチを復旧する

① ローカルで-Dしてしまった直後

reflogから即復旧
# 削除直前のHEADを探す
git reflog
# a1b2c3d HEAD@{0}: checkout: moving from feature/X to main
# d4e5f6c HEAD@{1}: commit: feat: 機能X実装          ← これが削除対象の最後のcommit

# 新しいブランチとして復旧
git branch feature/X d4e5f6c

# または直接checkout
git checkout -b feature/X d4e5f6c

② 時間が経ってreflogから消えた

fsckで孤児コミットを探す
# 到達不可能なオブジェクトを探す
git fsck --lost-found
# dangling commit a1b2c3d...
# dangling commit d4e5f6c...

# コミット内容を確認
git show d4e5f6c

# 使えるものを復旧
git branch restored/feature-X d4e5f6c

③ リモート削除した直後(GitHub)

GitHubの削除ブランチ復旧

GitHubはWeb UIで削除したブランチを一定期間(目安:数分〜1時間)復元可能。Pull Requests → Closed → 該当PRを開く → “Restore branch” ボタン。PR経由で作ったブランチなら復旧しやすい。AdminのAudit logから復旧依頼できる場合もあり。

④ push前に-Dした場合

ローカルのreflogにしか記録が無く、他のPCにcloneしていなければ復旧はreflogfsckが唯一。重要なブランチはpushを習慣化するのが最良の保険です。

復旧できないケースを減らす最強の運用:①個人ブランチも原則push(普段の作業用origin/以下に置く)、②重要な作業前にgit stashまたは一時commit③gcの自動pruneを抑制git config gc.pruneExpire=2.weeks)。

削除トラブルを未然に防ぐチーム運用ルール

エラー対処の知識よりも、そもそもトラブルが起きない運用を整えるのが効率的です。以下はCONTRIBUTING.md / 運用ガイドに組み込むべきルール集。

削除ルールのテンプレート
## ブランチ削除ルール

### 削除OKなブランチ
- feature/* : PR marge後に自動削除(GitHub Auto-delete branch有効化)
- fix/*     : PR merge後に自動削除
- tmp/*     : 個人の一時ブランチ、自由に削除可

### 削除NGなブランチ
- main / master : 保護ブランチ、削除不可
- release/*    : Branch protectionでdelete禁止、代わりにタグでアーカイブ
- hotfix/*     : PR merge後に Admin判断で削除

### 削除前チェックリスト
1. git log <branch> --not main --oneline で未マージコミット有無を確認
2. GitHub/GitLab の Open PR にまだ残っていないかチェック
3. CIビルドが完了してから削除(実行中のCIが失敗する可能性)
4. 他メンバーが参照していないか Slack等で確認

### Branch protection 推奨設定(mainブランチ)
- Restrict deletions: ON
- Require pull request reviews: 1+
- Require status checks: CI green必須
- Include administrators: ON(Adminでも保護対象)

GitHubの自動削除設定

Settings → General → Pull Requests → Automatically delete head branchesを有効化すると、PRがmerge/closeされた瞬間にそのfeatureブランチが自動削除されます。ゴミブランチが溜まらず、削除漏れの事故も減るためほぼすべてのリポジトリで有効化推奨。マージ済みhead branchはPRから”Restore branch”ボタンで即復旧できるため安全。

よくある質問

Q-dと-Dの違いは?
A-dはマージ済み確認付き削除、-Dマージ状態を無視した強制削除。日常はまず-dで試し、エラーで止められたら本当に不要か確認してから-Dに切り替える、が正しいフロー。-Dを手癖にすると、squash mergeと誤解して必要なコミットを失います。
Qgit push origin :feature/X と –delete feature/X は同じ?
A結果は同じです。git push origin :<name>は「空のrefをpush=削除」という古い書き方、git push origin --delete <name>(Git 1.7以降)は同じ動作の可読性の高い新書式。現在は--delete推奨。コロン記法はエイリアスやスクリプトでたまに見かけますがメンテナンス性のため新規コードでは使わない方が良いです。
Qmerge済みと言われるのに-dで削除できない
A-d現在のHEADから辿れるかで判定します。mainにマージしたあと自分がfeatureブランチに戻ってから-dすると、featureからはmainの最新が辿れないので「未マージ」と出ることがあります。git switch mainしてからgit branch -d featureが正解。
Qリモートブランチをローカルから削除したい
Agit push origin --delete <name>がリモート側削除コマンド。git branch -d -r origin/<name>ローカルに残っているリモート追跡参照を消すだけで、リモートサーバー上のブランチは消えません。意図通り使い分けを。詳細は【Git】ブランチを削除する方法参照。
Qマージ済みブランチを一括削除したい
A次のワンライナーで一括削除できます。mainとdevelopは除外。git branch --merged main | grep -vE "^\*|main$|develop$" | xargs -n 1 git branch -d事前にgit branch --merged mainだけ実行して対象を確認するのが安全。PowerShellならgit branch --merged main | Where-Object {$_ -notmatch "main$|^\*"} | ForEach-Object { git branch -d $_.Trim() }
Q削除したはずのブランチがGitHubのPRに残っている
AClosed/Mergedになった過去PRにはブランチ名とcompareリンクが残る仕様。復元ボタンから復活もできます。これはGitHubの意図的な設計で、PRを後から追跡・復旧しやすくするため。完全に履歴ごと消したい場合はPR自体の削除(権限が必要)か、リポジトリのtransferやarchiveを検討してください。
Q誤って-Dしたブランチを何も知らない状態から復旧したい
A最短フロー:①git reflogで削除直前のHEAD@{N}を探す。②git checkout -b 復旧名 HEAD@{N}でブランチ再生成。③もしreflogに無ければgit fsck --lost-foundで孤児コミットを探す。④リモートに残っていた場合はgit fetch origin 元の名前:復旧名で取得。詳しくは本記事「誤削除したブランチを復旧する」セクション。
Qマージ済みかわからない状態で削除していいか
Agit log <branch> --not main --onelinemainに取り込まれていないコミットが見えます。出力が空なら実質マージ済み(squash経由含む)。出力にコミットがある場合は、cherry-pickで必要分を救出してから-D。安全策としてgit branch --contains <branch>の先端shaでどのブランチにマージされているかも確認できます。

関連記事

まとめ

  • 削除エラーは12パターン。エラーメッセージ早見表で30秒以内に原因特定できる
  • 最頻出は「checked out at」「not fully merged」「protected branch」の3つ
  • 未マージ警告はsquash merge由来のことが多く、git log feature --not main --onelineで実質マージ済みかを確認
  • worktree・submodule・default branch・タグ衝突など、表面的に見えない原因も意外と多い
  • リモート削除が反映されない時はfetch --prunefetch.prune=trueで自動化
  • 誤削除はreflogfsck --lost-found→GitHubの”Restore branch”の順で復旧試行
  • GitHubのAuto-delete head branches設定で削除漏れを未然防止
  • Branch protectionでmainなど重要ブランチの削除を物理禁止+CONTRIBUTING.mdでルール明文化

ブランチ削除エラーは表面的には複雑に見えますが、原因は有限(12パターン以内)。エラーメッセージ早見表で即座に原因特定し、-Dでの安易な強制削除を避ければ、未マージコミットを失う事故は未然に防げます。最強の運用は①Branch protection+②Auto-delete+③fetch.prune自動化+④reflog依存の最終保険。この4つが揃えば、削除トラブルはほぼゼロにできます。