Prune Git's Unreachable Objects Without Guessing
You will preview, then remove, unreachable objects from a Git object database while keeping a chance to recover from mistakes. Allow 10 to 15 minutes for a repository you understand. You need Git installed and write access to the repository; no elevated privileges are normally required.
The route
Jump straight to the step you need, or tick off Done means at the end.
This guide describes Git 2.43.0, the version installed on this machine. Run git --version first if the command will be used in a script or on several hosts. Direct pruning is destructive: once an unreachable object has been deleted, a reflog or dangling commit cannot bring it back.
1. Enter the right repository
Change to the working tree or bare repository that you intend to maintain. A useful first check confirms both the repository and the Git version:
$ cd /path/to/repository
$ git rev-parse --show-toplevel
/path/to/repository
$ git --version
git version 2.43.0
Do not run this from a parent directory and rely on memory. The command acts on the repository Git discovers from the current directory. If the repository is bare, git rev-parse --is-bare-repository should print true; the remaining checks still apply.
Checkpoint: confirm that the printed path is the repository you mean to change. If it is wrong, stop and correct cd before continuing.
2. Preview what direct pruning would remove
Start with a dry run. The -n option reports candidates without removing anything. Adding -v asks for all removed objects to be reported, which also makes the preview useful when deciding whether the result is plausible:
$ git prune --dry-run --verbose
<object-id>
<another-object-id>
The output is a list of object IDs, one per line. An empty result is valid: there may be nothing old and unreachable for this command to remove. Do not treat every object shown as disposable until you have checked for other repositories that borrow this object database.
3. Check references and borrowed object stores
Git keeps objects reachable from refs under refs/. Reflogs and other temporary recovery paths are not themselves the rule described by git prune, so the safe age window and the repository's normal maintenance policy matter. An object can look unused from the current branch while still being needed by a recently deleted branch, a recovery workflow or another repository.
Inspect the repository's alternate-object configuration before proceeding:
$ git rev-parse --git-path objects/info/alternates
.git/objects/info/alternates
$ test -s "$(git rev-parse --git-path objects/info/alternates)" && \
sed -n '1,20p' "$(git rev-parse --git-path objects/info/alternates)"
If that file names an object store used by another repository, use the documented pattern from the local manual. Supply every object reachable from the borrowing repository as an extra head:
$ git prune --dry-run --verbose \
$(cd /path/to/another-repository && git rev-parse --all)
That command preserves objects reachable from the other repository as well as objects reachable from refs in the current repository. The command substitution must be reviewed before execution: a wrong path or an unreadable repository can make the protection incomplete.
4. Limit pruning by age
Use --expire when you want to remove only loose objects older than a chosen expiry time. For example, this preview limits candidates to objects older than 30 days:
$ git prune --dry-run --verbose --expire="30 days ago"
<object-id>
The exact expiry-date syntax is interpreted by Git. Use an explicit date if a repeatable maintenance job needs a fixed boundary, for example 2026-09-01. The age filter applies to loose objects. Unreachable objects already packed remain; removing those requires a separate repack decision, not an extra git prune flag.
Checkpoint: run the same dry run with the final repository path, alternate heads and expiry value. Compare the count and, where practical, the object IDs with your expectation.
5. Perform the deletion only after the preview
When the preview is understood, remove the candidates with the same arguments, dropping --dry-run:
$ git prune --verbose --expire="30 days ago"
<object-id>
Git also invokes git prune-packed as part of this operation and removes unreachable entries from .git/shallow when they are not reachable from any ref. The command does not need sudo. Running it as root can leave files owned by root and hides permission problems that should be fixed at the repository boundary.
If the command fails, keep the error output and do not immediately repeat it with broader permissions. Check the repository path, ownership and available storage first. A failed run does not provide a rollback for objects already removed in an earlier successful run.
6. Prefer normal housekeeping when direct pruning is not required
For most repositories, the local manual recommends git gc instead. It coordinates pruning with other housekeeping work and is the better default when you are not deliberately maintaining an object database or testing a recovery boundary:
$ git gc
$ git count-objects -vH
git gc can repack and expire data according to Git's maintenance settings, so read its documentation and repository policy before using it on a busy or shared checkout. Do not run cleanup concurrently with a process that is writing to the same repository unless your operational procedure explicitly permits it.
7. Verify the repository after pruning
Check object connectivity without changing the repository:
$ git fsck --full --no-reflogs
<diagnostics vary with the repository>
The progress counts vary with the repository, and warnings about intentionally unreachable data may be expected in a repository that uses a special workflow. Investigate unexpected missing objects, broken links or newly reported errors before treating the maintenance as complete. Keep any external backup or clone until the repository has been used successfully.
Done means
- You verified the repository path and Git version.
- You ran a dry run before any deletion and reviewed its candidates.
- You checked alternate object stores and supplied extra heads where another repository borrows this one.
- You used an explicit expiry boundary when age mattered.
- You ran connectivity checks afterwards and retained a recovery copy where the objects mattered.