DEV Community

Colin Maxwell
Colin Maxwell

Posted on AI-assisted

The 31.5GB a killed git process left behind

The 31.5GB a killed git process left behind

I'd been running out of disk space for months and assumed it was caches. It wasn't. It was git.

A repack had been interrupted — probably a laptop lid closed at the wrong moment — and left behind 21 orphaned tmp_pack_* files totalling 31.5GB. They'd been sitting there since.

Here's the part that matters: git gc --prune=now does not remove them. git count-objects reports them as size-garbage, which is accurate, but they aren't in the object database, so pruning never touches them. They sit there forever, in plain sight, until something unlinks them explicitly.

I wrote a tool to handle that class of problem. Not general disk cleaning — cleaning up after dev tooling.

What actually accumulates

The space isn't in your caches. It's in the places tooling forgets to tidy:

Git repositories

  • Orphaned tmp_pack_* / tmp_idx_* from interrupted repacks (the 31.5GB above)
  • Unreachable objects after branch churn, reclaimed with reflog expire then gc --prune=now
  • Both are the same symptom, different cause, and gc only fixes one

OpenClaw and agent runtimes

  • Timestamped working directories under tmp/, one per run, never reaped
  • Node compile caches that grow unbounded
  • Control UI asset caches duplicated on every update
  • Staging directories from failed backup runs, which are the worst kind: they often hold an unencrypted copy of everything

Build and tool caches

  • DerivedData from Xcode, which can reach tens of GB on its own
  • Flutter's .dart_tool and build directories, one per project
  • __pycache__ scattered across virtualenvs

By the time you notice, you're deleting the wrong things to make room — and usually the wrong things are your archives.

The rule it's built on

A deletion is only safe if its replacement is guaranteed.

That's not a marketing line, it's the constraint that decides every case. It splits what's found into four tiers:

  • Safe — regenerable by definition. Build output, caches.
  • Rotate — live logs. Truncated, not deleted, because the process holds an open handle and deleting it frees nothing while logging silently breaks.
  • Verify — backups. Refused unless a readable copy is proven elsewhere.
  • Git — objects and orphaned packs, reclaimed without touching commits.

The verify tier is where I learned the most.

A backup you can't read is not a backup

My own backup script exited 0, printed == DR backup complete, and wrote a 405MB file to iCloud. It was a placeholder — the upload was accepted into a local buffer and never completed, so the file later read as zero bytes.

Nothing noticed for seven weeks. Every scheduled run since had failed at the encryption step, because the passphrase only existed as an environment variable and cron has no environment.

So Moult checks two separate things:

  1. Is there a usable snapshot here at all? A folder from a run that failed before encryption holds an unencrypted tarball and a stub. It protects nothing and stores plaintext of everything. Safe to remove.
  2. If there is a usable snapshot, does a verified copy exist off this drive? Only then is it protected.

Treating those as one question is how a failed backup comes to look identical to a good one.

Two bugs that were entirely my own

A hardcoded number. A view displayed "31.5GB of unreachable objects" as a literal string. After reclaiming the space, the app kept claiming it was there. Every number now comes from a real scan.

A --json flag that wasn't JSON. Progress text was appended to stdout alongside the JSON. JSONSerialization failed with "extra data", the app got an empty list, and every button did nothing. It looked like a broken UI for an afternoon. It was a one-line CLI bug.

Both are the same failure: something reporting success while doing nothing.

What it found on my machine

67GB total. The 31.5GB of orphan packs, 370MB of gateway logs, the rest in caches and run directories I'd never looked at.

If you work with git, agent runtimes, or a build toolchain, there's probably something similar behind you.

What it doesn't do

  • Not sandboxed. A tool that reads across your filesystem can't meaningfully be sandboxed — it's signed and notarised instead.
  • No network code. Not "privacy-focused"; there is no networking in the binary.
  • It refuses a lot. If it can't prove something is safe, it says so and leaves it. Some people find that annoying. It's the entire point.

Trying it

tooled.pro/moult — free, macOS 14+.

The useful thing to watch isn't the total. It's what it declines to touch, and the reason it gives.


What changed from the first draft:

The framing is now "cleaning up after dev tooling" rather than "disk cleaner". The 31.5GB orphan-pack story leads, because it's specific, surprising, and immediately recognisable to anyone who's had a git repack die. OpenClaw's runtime debris gets its own section, which matters given dev.to's audience overlaps with agent tooling.

I dropped the CleanMyMac/DaisyDisk comparison entirely — competing with them was the wrong frame, since Moult deliberately doesn't behave like them.

Let me know what you think in the comments, any suggestions would be greatly appreciated.

Thanks.

Top comments (0)