Skip to content

v2.3.2

Choose a tag to compare

@AkashGoenka AkashGoenka released this 31 Aug 18:44
· 35 commits to main since this release
d5e9f9d

Fixes an index build that could hang forever on a single file, and makes that whole class of failure diagnosable.

Fixed

An index build could hang forever on a single file, with no error and no way to tell which file. The shape gate deciding whether a token looks like an identifier used a catastrophic-backtracking regex — a quantified group inside a trailing +. One long mixed-case run that ends up failing the match sends the engine exponential. Measured growth is 2× per 2 characters: 46 characters takes 41ms, 66 takes about 42 seconds, ~90 takes longer than anyone will wait.

The trigger is narrower than it looks, which is why it surfaced in real fixtures: the token scanner accepts _ but the camelCase pattern does not, so only a mixed-case run containing an underscore can fail this way — base64url blobs and JWT signatures exactly. A pure alphanumeric camelCase run matches quickly and is harmless. Extraction runs on every file in every language, so the repo's language was never the point.

While stuck, the keeper span at 100% CPU holding its lock, the log stopped after Parsing files..., init blocked for its full 180s, and killing the process was the only recovery — after which the next build hit the same file and hung identically.

The four shape patterns are now written without nested quantifiers, with a token-length cap as a second line of defence. Verified equivalent to the old patterns across 400,018 randomly generated tokens plus the existing corpus: zero disagreements.

coldstart status reported work that had stopped long ago. A keeper killed mid-rebuild leaves its inProgress stamp on disk, and readers are meant to check the recorded pid is alive before believing it — waitForCacheHead did, status did not. Since killing the keeper was the only escape from the hang above, the one command you would reach for while diagnosing it was guaranteed to mislead you.

Added

A build that gets stuck now tells you which file it got stuck on. This failure blocks the event loop, which rules out every usual approach — a watchdog timer, a progress interval, a signal handler and any async write are all structurally unable to run once the spin starts. So the indexer writes what it is about to do before doing it, synchronously, and clears the record on clean completion, which means a record outliving its own process is itself the diagnosis.

working on: parse app/models/poison.rb (17s ago)
LAST SEEN: PID 55760 died during parse app/models/poison.rb — 18s ago, never finished

The next keeper repeats it as a warning at startup and appends died-in-progress to repair.jsonl, because startup is the last moment that evidence exists before the next build overwrites it. The record is written per file rather than per batch: 86ms across a 2000-file build, under 3% of a build that takes seconds, in exchange for the exact path instead of a 100-file window to bisect by hand.

Upgrading

If you were hit by the hang, upgrading alone is not always enough — a cache directory left half-written by a stuck build can persist. Remove ~/.coldstart and re-run any coldstart find to rebuild it from scratch.

Full changelog: v2.3.1...v2.3.2