v1.108.172 — opt-in idle index-cache TTL + process presence registry
Instance sprawl was invisible, and each abandoned process sat on its own hydrated indexes.
Follow-up to #375. A user found 25+ live jcodemunch instances on one box against a single ~140MB index store, ages up to 1d15h, mostly stdio servers their MCP client never reaped at session end. Reaping the day-plus-old ones freed ~17 GB of RAM.
The leak itself is not ours: every background thread is already daemon=True, and run_stdio_server exits on stdin EOF. We cannot reap another program's children. The memory is ours, and so is the fact that nobody could see the sprawl.
Added
JCODEMUNCH_INDEX_CACHE_TTL, opt-in and off by default. _index_cache held up to 32 hydrated CodeIndex objects, released only under LRU pressure and never on idle. #370 was the same fact from the other end: one worker at ~16 GiB.
It stays off by default deliberately. Cold hydration of that 665k-symbol index was measured at 7.5 to 11.4 minutes, so evicting during a quiet spell hands the next query that bill. Defaulting it on would fix the leaking box by breaking the large-index one. Unset, 0, negative and unparseable all disable it, byte-identical to previous behavior. Swept on access rather than by a timer thread.
A process presence registry. Each server writes one small ~/.code-index/_processes/<pid>.json (pid, client, transport, version, start time — no repos, paths or queries) and removes it on exit. Readers prune by PID liveness, so a hard kill leaves nothing behind. Surfaced as a processes block in get_session_stats, with a hint only past 5 live processes. Disclosed in the README background-behavior section.
Fixed
_is_pid_alive reported dead Windows processes as alive. A successful OpenProcess does not mean the process is running: while any handle to it remains open the PID stays queryable after exit, and the spawning parent normally holds exactly such a handle.
This is shared machinery. process_locks.inspect treats a dead holder's lock as stale and ignorable, so a crashed server's lock file could look permanently held. Now checks GetExitCodeProcess for STILL_ACTIVE, falling back to the old answer if that call fails rather than declaring a possibly-live process dead. Also adds the missing argtypes/restype: OpenProcess returns a pointer-sized HANDLE and ctypes defaults to c_int, truncating it on 64-bit.
New tests/test_v1_108_172.py (21), including a real spawn-and-kill check that fails against the pre-fix logic. The 63 existing lock tests stay green. No schema, tool-count, or INDEX_VERSION change.