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
| Cache | Before | After | Note |
|---|---|---|---|
~/.docker/models | 22 GB | 2.6 GB | gemma4 removed; qwen is load-bearing |
~/.npm | 8.1 GB | 15 MB | npm cache clean --force |
~/.m2 | 1.7 GB | gone | Maven repo, last write Feb 2024; mvn/gradle not installed and java is now only the macOS stub |
~/.cache/claude-ops | 2.3 GB | 1.1 GB | stale sessions.duckdb.bak-20260605 |
~/.cache/puppeteer | 511 MB | gone | dead 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
javaunused). mdlson 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.
Related
- 2026-07-13 The Intel Audit That Could Not See Shims — the same investigation, one axis over
- 2026-07-13 Darwin Nixpkgs Regressions Blocking hm switch