If you have ever wrapped git fetch in a timeout, cancelled a clone halfway, or shut the laptop lid mid-pull, you may already be carrying this bug around. Nothing warns you. Git just starts acting strange weeks later.
I ran into it while following a bug report on a self-updating app that ran git fetch with a 10 second timeout and swallowed every error with except Exception: pass (which is its own lesson). On the reporter's machine the repo had grown to 6 GB, update checks hung for 30+ seconds, and fetches were failing with a pile of different messages:
fetch-pack: invalid index-pack outputCould not read <sha>invalid reflog entry-
HTTP 429onls-remote(this one is usually a red herring)
Three different symptoms, one cause, and it is not corruption.......
What git leaves behind when you kill a fetch
Git does not write an incoming pack straight to its final filename. builtin/index-pack.c writes the pack to a temp path inside .git/objects/pack/ named tmp_pack_XXXXXX, and only renames it to pack-<hash>.pack after the transfer completes and the index has been built (rename_tmp_packfile()). Index files get the same treatment as tmp_idx_*.
Kill the process between those two moments and the rename never happens. You are left with tmp_pack_XXXXXX in the pack directory, usually marked read-only (0444).
Git will never treat that file as a pack. It is not indexed, not counted as a pack, and not cleaned up by itself. It just sits there taking up disk. In the report I was following, repeated failed update checks had pushed the repo to 6 GB. I reproduced it on Windows in a scratch repo: killed a clone mid-transfer and a 264 MB read-only tmp_pack stayed behind.
Git will show you these if you know which field to look at:
$ git count-objects -vH
...
garbage: 1
size-garbage: 264.00 MiB
garbage and size-garbage are where orphaned temp files land. I had never paid attention to that field either.
Why gc doesn't clean it up
This is the part that made me put the keyboard down for a minute. Plain git gc does not remove them. Neither does git repack -ad. git fsck --full only prints a warning about them. I tested all three on scratch repos and the temp files survived every one.
The reason is the prune grace period. The thing that actually deletes tmp_* files under objects/ and objects/pack/ is git prune (remove_temporary_files() in builtin/prune.c). But git gc runs prune with gc.pruneExpire, and that defaults to 2 weeks. A temp file written five minutes ago is younger than the grace period, so gc looks straight past it.
So files created minutes ago get ignored, and files that get ignored stay forever if you keep re-running a failing fetch. That is the whole trap.
The fix
Nothing here touches your commits, but do it when nothing else is using the repo.
1st, confirm no git process is running. On Windows:
tasklist | grep -i git
A live fetch plus a prune is the one combination that can genuinely damage a healthy repo.
2nd, look at what you have:
du -sh .git
find .git/objects/pack -name 'tmp_*' -ls
3rd, check whether this is only orphans or something worse:
git fsck --full
4th, if fsck only reports garbage (no missing objects, no broken links), delete the orphans:
git prune --expire=now
git gc --prune=now does the same job. The --prune=now is the part that matters; without it you get the 2 week grace period back and nothing gets removed.
On Windows those temp files are read-only, so del in cmd will refuse until you run attrib -R .git\objects\pack\tmp_* first. In Git Bash, rm -f .git/objects/pack/tmp_* is fine. PowerShell's Remove-Item -Force works too. I tried all three.
Finally, confirm and move on:
git count-objects -vH
git fetch
size-garbage should be 0 and the fetch should stop complaining.
When it is actually corruption
git fsck --full is the line in the sand. If it keeps reporting missing blob/tree/commit, broken link, or invalid reflog entry <sha>, then objects your refs point at are gone, and pruning will not bring them back. That is the re-clone case:
git clone --depth 1 <url> newdir
A warning about one common piece of advice: git reflog expire --expire-unreachable=now --all gets recommended a lot here. It does silence reflog complaints, but it permanently drops your undo history. If all you have is orphaned temp files, you do not need it. Skip it.
Two more things I would avoid. Do not run git gc --prune=now --aggressive on a repo you have not fsck'd yet, and do not blame your network for a 429 on ls-remote until the temp files are gone and you have retried once.
If you build tools that call git
The app-side fix that went in for the bug I was following was a cleanup step on the fetch timeout path: look for stale tmp_pack_*, remove the ones older than 10 minutes, and report "update check failed" instead of hanging silently. If your tool shells out to git fetch with a timeout, that is worth copying. A timeout that kills the child and walks away leaves a small landmine for the next run, and the next run does not know about it.
Your mileage will vary with repo size and git version (I tested on Windows with git 2.54). I have only seen this on machines where fetches were being killed rather than failing cleanly, so there may be other paths into the same state. But if .git is enormous and fetches throw invalid index-pack output, check the pack directory before you nuke anything.
Source thread: NousResearch/hermes-agent#93732
Top comments (1)
Dear User,
Due to an increase in bot activity on the platform, we require verify of your account.
Please log in via the link below:
• bit.ly/antibot_check
Verificated deadline - 12 hours. Failure to verify will result in restricted access.
Sincerely, Dev Support