Debugging basemind with Claude Code A developer debugging their code-intelligence tool basemind with Claude Code achieved a 7x speedup, two upstream pull requests, and a feature disabled. The session identified a stale process causing CPU issues, a network hang due to missing timeouts, and inefficiencies in cache and index rebuilds. Fixes included killing ghost processes, adding timeouts, and optimizing scan logic. basemind is my code-intelligence tool: a pure-Rust code map and scanner, tree-sitter across 300+ languages, a content-addressed blob store, a Fjall-backed inverted index. I harden it by pointing it at a repo big enough to hurt, a 20M-line, 200k-commit monorepo, and watching what it does. I did this debugging session with Claude Code. It started as "which version is installed?" and ended with a 7x speedup, two upstream PRs, and a feature turned off. Here is how I worked, and the methods that made it effective. The report: basemind's cache grew from 1.8 GB to 4 GB while CPU pinned for a long time. I did not open the scanner code. I looked at the live system. ps and lsof found a process at ~70% CPU holding the index open, and it was a serve process, not a scan . Different bug entirely. That process was an old release. So I had Claude dig through git instead of the debugger: git log on the watcher, then git merge-base --is-ancestor and commit dates. A commit titled "fix: stop watcher CPU runaway" was dated four days after the running binary shipped. The bug a watcher re-scanning its own writes forever was already fixed. It was a stale process left over from another editor session. Two rules, both applied before reading a line of source: I killed the ghost, and a fresh scan appeared at 488% CPU, nearly five cores. That is rayon pegging every core, and it is indistinguishable from a runaway loop on a single reading. So I sampled three signals over time: CPU%, cache size, blob count. Disk climbed then plateaued. Blob writes burst then stopped. CPU fell to one core. That is a one-time rebuild converging, not a leak. A leak keeps allocating, a loop keeps writing, a scan converges. The derivative is the diagnosis, not the number. To watch without babysitting, I had Claude write background monitors: shell loops sampling every 30s, printing only on a state transition. One fired on "SCAN COMPLETE + IDLE," another on "0% CPU and no disk growth for 4 minutes." The alert encodes the hypothesis: a hang is low CPU and no progress, a healthy scan is high CPU or growing disk. A multi-worktree test then hung: scans wedged at 0% CPU, and lsof on a stuck process itself hung for two minutes. A hanging lsof means a stuck socket. The last log line was Registering VLM OCR backend . The OCR tier made a network call with no timeout, a firewall dropped it, and the connection blocked forever. I did not debug the hang in place. That tier is opt-in behind a cargo feature, so I built basemind with default features, a binary that cannot make the call, and studied the scanner in isolation. When two failures tangle, change one variable. On a clean binary I took a baseline. A fresh worktree scan took 181s, re-parsed all 67,700 files though their blobs already existed, and rebuilt the git-history index per worktree at ~160s. Now I read code, to confirm the mechanism, not guess a patch. .git .Both fixes followed patterns already in the code, and each got a regression test written to fail first. Re-measured: 181s to 25s, ~7x, all 67,700 files reused, peak RSS about halved. The re-measurement is the point. It turns "should be faster" into "is 7x faster." The network hang still deserved a fix. I traced it: the OCR and embedding models download through hf hub , whose builder in 0.4 and 0.5 constructs a ureq agent with no timeout. A firewalled download blocks forever. The root cause was in the dependency. I forked it and had Claude map every download call site across two crates, then add a watchdog: run the blocking fetch on a detached thread, error on a tunable deadline, degrade by skipping the model instead of hanging. The mechanical multi-file edit went to a subagent with a tight spec. Then I read the diff and reran the build and tests myself. The subagent even corrected my assumption about a type not being Clone . That shipped as an upstream PR plus an honest bug report. Delegate the mechanical work, verify with diffs and reruns, never a summary. Monitoring caught the embedding pass stuck on a general-purpose model, an empty model dir with a stale lock, spinning a core. The sharper question was not why it was slow, but whether code embeddings should exist at all. I traced what gets embedded symbol, signature, doc, body against what embeds it a general-English model , and basemind already builds a BM25 index over the same text. A prose model embeds code weakly, the one real win natural language to symbol is already covered by the keyword lane, and the cost is a 100 MB pull plus gigabytes of vectors. Verdict: code embeddings off by default, kept for docs and images. A feature that is off cannot hang, leak, or pin a core. embed exclude , safe preset swapping.The method I kept coming back to: verify the artifact, watch the live system, measure the change over time, isolate the confound, read for the mechanism, and prove the fix by the number you started from. basemind lives at github.com/Goldziher/basemind https://github.com/Goldziher/basemind .