2026-07-14 Cache Audit: mtime Is Not Usage

What I set out to do

Fell out of 2026-07-13 The Intel Audit That Could Not See Shims. Adam noticed the pattern — “seems like we keep finding things in cache” — and asked to sort $HOME by size and staleness to see what was discardable. ~28 GB reclaimed, and one near-miss that is the whole lesson.

The near-miss: I nearly deleted the local model stack

~/.docker/models was 22 GB, last written 2026-04-09 (three months cold), holding two LLMs: gemma4:31B (17.5 GiB) and qwen3.5-4b (2.5 GiB). Ten docker model invocations in all of shell history. Every signal said dead experiment, and I was one message away from recommending the lot.

Then I checked what was listening:

lsof -iTCP:11434  ->  com.docker  (LISTEN)

Docker Model Runner serves on 11434 — the Ollama-compatible port. And litellm has an ollama_chat/* route pointed at http://localhost:11434. And the permission-suggestion hook’s default is literally:

runtime_model = "ollama:ollama_chat/huggingface.co/unsloth/qwen3.5-4b-gguf"

So the chain is permission hook → litellm :4000 → Docker Model Runner :11434 → that exact qwen model, answering right now. ollama itself is not installed anywhere — Docker is impersonating it on its port, which is exactly why grepping for “ollama” finds a config that looks dead and isn’t.

Why the mtime lied: model blobs are read, never written. A three-month-old mtime on a 17 GB weights file means “not re-downloaded since April”, not “not used since April”. gemma4 really was an experiment (Adam confirmed) and went out via docker model rm — using Docker’s own tooling so the blob store and manifests stay consistent — leaving qwen intact. 22 GB → 2.6 GB.

The rest of the sweep

CacheBeforeAfterNote
~/.docker/models22 GB2.6 GBgemma4 removed; qwen is load-bearing
~/.npm8.1 GB15 MBnpm cache clean --force
~/.m21.7 GBgoneMaven repo, last write Feb 2024; mvn/gradle not installed and java is now only the macOS stub
~/.cache/claude-ops2.3 GB1.1 GBstale sessions.duckdb.bak-20260605
~/.cache/puppeteer511 MBgonedead since Nov 2025

Plus ten dead dotdirs (.putty 2021, .conda, .bash_sessions 2020, .vue-cli-ui, .pylint.d, .pgadmin, .lemminx, .redhat, and two empty ones) — ~1.7 MB, pure hygiene.

uv cache prune could not run: three uv processes hold ~/.cache/uv/.lock — the uv-based MCP servers, alive for the whole session. It offers --force and I did not take it: those servers’ environments are hardlinked into that cache, and pruning underneath a live process is precisely how you break something quietly. Run it when Claude Code is not running. Still 19 GB on the table.

Kept, and the reasons matter: ~/.cups looked like junk but holds the default printer (Brother_DCP_L2550DW_series). ~/.aws holds 8 plaintext credential entries from 2021 — flagged rather than deleted, because deleting the file does not revoke the keys. Revoke in IAM first; the local delete is the second step, not the fix.

Final Intel inventory: 47

Only one is actionable: 20 files in Google Drive’s trash (my coursework deletions, awaiting a trash-empty). The rest: RVS046.mac (kept by request), 15 inert vendor prebuilds, 8 browser DRM plugins, and amm.app.

I verified the vendor ones rather than assuming — VS Code ships copilot-darwin-arm64 alongside copilot-darwin-x64, and Node demonstrably resolves the arm64 prebuilds for gemini-cli.

One real bug fell out of that check. Obsidian runs native ARM64 but bundles btime and get-fonts as darwin-x64 only — no arm64 sibling, unlike every other package. A Mach-O native module cannot load into a process of a different architecture, so those two are silently dead in Obsidian: btime reads file creation times, get-fonts enumerates system fonts. An upstream packaging gap, not something to clean up — but it likely explains any wrong “created” dates. Fourth instance today of the same fact: an x86_64 binary in an arm64 host is not slow, it is silently absent.

What was striking

Yesterday’s lesson was runtime indirection is invisible to static inspection. Today’s is its sibling: staleness is not usage. Both are the same failure — evidence that cannot see the thing it is being asked about:

  • atuin sees shell history → blind to GUI apps (called java unused).
  • mdls on a path that does not exist → (null), which I read as “never used” (VS Code).
  • mtime on a blob store → cold, because weights are read not written (nearly deleted the model stack).

Three wrong calls, one root cause. The fix is never a better heuristic; it is going and asking the running system: lsof the port, lsappinfo the arch, curl the endpoint.