<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Colin Maxwell</title>
    <description>The latest articles on DEV Community by Colin Maxwell (@colin_maxwell_ec01d35c068).</description>
    <link>https://dev.to/colin_maxwell_ec01d35c068</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3354515%2Feb54a72f-f349-420d-933d-61b54a1074a6.png</url>
      <title>DEV Community: Colin Maxwell</title>
      <link>https://dev.to/colin_maxwell_ec01d35c068</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/colin_maxwell_ec01d35c068"/>
    <language>en</language>
    <item>
      <title>The 31.5GB a killed git process left behind</title>
      <dc:creator>Colin Maxwell</dc:creator>
      <pubDate>Sat, 07 Nov 2026 19:17:46 +0000</pubDate>
      <link>https://dev.to/colin_maxwell_ec01d35c068/the-315gb-a-killed-git-process-left-behind-4daa</link>
      <guid>https://dev.to/colin_maxwell_ec01d35c068/the-315gb-a-killed-git-process-left-behind-4daa</guid>
      <description>&lt;h1&gt;
  
  
  The 31.5GB a killed git process left behind
&lt;/h1&gt;

&lt;p&gt;I'd been running out of disk space for months and assumed it was caches. It wasn't. It was git.&lt;/p&gt;

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

&lt;p&gt;Here's the part that matters: &lt;strong&gt;&lt;code&gt;git gc --prune=now&lt;/code&gt; does not remove them.&lt;/strong&gt; &lt;code&gt;git count-objects&lt;/code&gt; reports them as &lt;code&gt;size-garbage&lt;/code&gt;, 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.&lt;/p&gt;

&lt;p&gt;I wrote a tool to handle that class of problem. Not general disk cleaning — cleaning up after dev tooling.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually accumulates
&lt;/h2&gt;

&lt;p&gt;The space isn't in your caches. It's in the places tooling forgets to tidy:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Git repositories&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;OpenClaw and agent runtimes&lt;/strong&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;strong&gt;Build and tool caches&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;DerivedData&lt;/code&gt; from Xcode, which can reach tens of GB on its own&lt;/li&gt;
&lt;li&gt;Flutter's &lt;code&gt;.dart_tool&lt;/code&gt; and build directories, one per project&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;__pycache__&lt;/code&gt; scattered across virtualenvs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the time you notice, you're deleting the wrong things to make room — and usually the wrong things are your archives.&lt;/p&gt;

&lt;h2&gt;
  
  
  The rule it's built on
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A deletion is only safe if its replacement is guaranteed.&lt;/strong&gt;&lt;/p&gt;

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

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

&lt;p&gt;The verify tier is where I learned the most.&lt;/p&gt;

&lt;h2&gt;
  
  
  A backup you can't read is not a backup
&lt;/h2&gt;

&lt;p&gt;My own backup script exited 0, printed &lt;code&gt;== DR backup complete&lt;/code&gt;, 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;So Moult checks two separate things:&lt;/p&gt;

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

&lt;p&gt;Treating those as one question is how a failed backup comes to look identical to a good one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two bugs that were entirely my own
&lt;/h2&gt;

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

&lt;p&gt;&lt;strong&gt;A &lt;code&gt;--json&lt;/code&gt; flag that wasn't JSON.&lt;/strong&gt; Progress text was appended to stdout alongside the JSON. &lt;code&gt;JSONSerialization&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;Both are the same failure: something reporting success while doing nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it found on my machine
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;67GB total.&lt;/strong&gt; The 31.5GB of orphan packs, 370MB of gateway logs, the rest in caches and run directories I'd never looked at.&lt;/p&gt;

&lt;p&gt;If you work with git, agent runtimes, or a build toolchain, there's probably something similar behind you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it doesn't do
&lt;/h2&gt;

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

&lt;h2&gt;
  
  
  Trying it
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;tooled.pro/moult&lt;/code&gt; — free, macOS 14+.&lt;/p&gt;

&lt;p&gt;The useful thing to watch isn't the total. It's what it declines to touch, and the reason it gives.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What changed from the first draft:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

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

&lt;p&gt;Let me know what you think in the comments, any suggestions would be greatly appreciated.&lt;/p&gt;

&lt;p&gt;Thanks.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>showdev</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
