Skip to content

Build1 publisher3 min readPublished

Git's two-week prune grace period keeps the tmp_pack files that killed fetches leave behind

Killed git fetches leave tmp_pack files that git gc ignores until they are two weeks old, a dev.to post found after tracing one user's 6 GB repo to them. Once git fsck confirms only garbage, an immediate prune clears them without touching commits.

The Engineer · Build desk

Illustration accompanying Git's two-week prune grace period keeps the tmp_pack files that killed fetches leave behind

What happened

  • A self-updating app that ran git fetch with a 10-second timeout grew one user's repo to 6 GB, with update checks hanging for more than 30 seconds.
  • Git's index-pack writes an incoming pack as tmp_pack_XXXXXX and renames it to pack-<hash>.pack only after the transfer and index finish, so a kill in between strands the temp file.
  • The author reproduced it on Windows, where a clone killed mid-transfer left a 264 MB read-only tmp_pack that git count-objects -vH reported as size-garbage.
  • In the author's tests, plain git gc and git repack -ad left the temp files in place, and git fsck --full only printed a warning about them.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint Scripts and apps that put a timeout on git fetch and retry on a schedule add orphaned packs faster than default gc settings remove them.
  • decision Reading these fetch errors as corruption leads straight to a re-clone; fsck output should make that call, and in an orphan-only repo a prune is enough.
  • cost The often-recommended git reflog expire --expire-unreachable=now --all quiets the errors by permanently deleting undo history an orphan-only repo never had to lose.
  • decision Any app that fetches on a timer has to ship its own tmp_pack cleanup, since git leaves the files in place; the fixed app in the report added exactly that step.

The rename happens in `rename_tmp_packfile()`, and index files get the same treatment under `tmp_idx_*` names [4]. Doing it last is the right design. Git never treats a tmp_pack file as a pack [6], so a half-downloaded file cannot be read as one. The cost lands when the process dies. The orphan is usually read-only, mode 0444 [5], and git neither indexes it nor cleans it up on its own [6].

Git does have code that deletes these files. It is `remove_temporary_files()` in `builtin/prune.c`, and it clears `tmp_*` files under `objects/` and `objects/pack/` [10]. The catch is the caller. `git gc` runs prune with `gc.pruneExpire`, which defaults to two weeks, so a temp file written five minutes earlier is too young to touch [11]. "So files created minutes ago get ignored, and files that get ignored stay forever if you keep re-running a failing fetch," the post's author wrote [12].

I think the two-week default is correct anyway. "A live fetch plus a prune is the one combination that can genuinely damage a healthy repo," the author wrote [13]. I'd expect the age check to be what keeps a routine gc away from a temp file that a running fetch is still writing.

The app in the report also swallowed every error with `except Exception: pass` [2]. It is an efficient way to fill a disk without anyone noticing.

Cleanup, per the post, runs in this order:

1. Confirm no git process is running. On Windows the author used `tasklist | grep -i git` [14]. 2. Measure with `du -sh .git` and `find .git/objects/pack -name 'tmp_*' -ls` [14]. 3. Run `git fsck --full` [14]. 4. If fsck reports only garbage, run `git prune --expire=now` or `git gc --prune=now` [15]. Without `--prune=now` the two-week window returns and nothing is removed [15]. 5. Check that `git count-objects -vH` shows `size-garbage` at 0, then fetch again [16].

The author wrote that none of this touches commits [23]. On Windows the read-only bit gets in the way. In cmd, `del` refuses until `attrib -R` clears it, while `rm -f` in Git Bash and `Remove-Item -Force` in PowerShell both worked when the author tried them [17].

Step 3 decides between cleanup and a re-clone. `invalid reflog entry` was one of the fetch errors on the reporter's machine, next to `fetch-pack: invalid index-pack output` and `Could not read <sha>` [3]. The same string is on the post's list of fsck results that mean objects your refs point at are gone, along with missing blob, tree or commit and `broken link`. If fsck keeps reporting those, pruning will not bring the objects back, and the author's answer is a re-clone with `git clone --depth 1` [18]. The author also warns against `git gc --prune=now --aggressive` before fsck has run, and against blaming the network for an HTTP 429 on `ls-remote` until the temp files are gone and the fetch has been retried once [20].

Both sizes in the post come from one bug report and one scratch-repo repro [1][7]. The post does not give the app's check interval or how much data each fetch pulled before the timeout hit. For growth like the report's to show up elsewhere, fetches would have to die mid-transfer over and over, with no gc at an immediate expiry in between [11][15].

What to watch

  • Any change in git that deletes tmp_pack files when index-pack is interrupted; that would move cleanup out of every tool that times out a fetch.
  • Whether the self-updating app's cleanup step checks for a live git process first, given the author's warning that pruning during a fetch can damage a healthy repo.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories