v0.25.1.md
docs/release-notes/v0.25.1.md
Tesserae v0.25.1 — the compile no longer wedges, and degraded docs are recoverable
<!-- translations:start -->
한국어 · 中文 · 日本語 · Русский · Español · Français · Deutsch
<!-- translations:end -->
Released 2026-07-25 · PyPI · GitHub release · pip install --upgrade tesserae==0.25.1
A bugfix release. No API changes — drop-in over v0.25.0, with one behaviour default flipped (see Upgrading).
Every fix here came out of one investigation. A compile on a real project sat at 0% CPU for 5 hours 43 minutes, behind a codex exec child that had been idle for 4 hours 6 minutes. It had already minted 32 COMMUNITY_SUMMARY nodes in memory and never lived to persist them, so graph.json ended with zero. Pulling that thread surfaced three more defects.
Fixes
- The extraction wedge guard is armed by default. v0.25.0 shipped a per-file extraction timeout, but opt-in — so it stayed switched off and the failure it prevents kept happening.
_run_cli's process-groupkillpgwas correctly wired the whole time; it simply never fired, becausecommunicate(timeout=None)never raises. The bound is now 1800 s per attempt by default. Killing the wedged child's process group let the parent compile resume within seconds — it had been parked incommunicate()for four hours.
A typo must not silently disarm a safety valve, so an unusable value (10m, 600s, negative, inf) now warns and keeps the default instead of falling back to "no cutoff". TESSERAE_EXTRACT_TIMEOUT=0 is the explicit escape hatch.
compile --retry-fallbacksrecovers docs that degraded to deterministic. When an extraction fails, the doc is served by the deterministic baseline — but its manifest entry was byte-identical to a clean extraction, so--changed-onlyskipped it on every later compile and the degradation was permanent until the file's own content changed. Recovering a handful of docs meant hand-editingmanifest.json, with the standing risk of instead deterministic-filling the whole corpus and destroying hours of typed work. Such entries are now marked and re-attempted on demand, mirroringdistill --retry-fallbacks.live_member_countongraph_mapcards.sizeandleaf_member_countboth come from thehierarchy.jsonsidecar, which is written mid-compile. Whengraph.jsonis rewritten afterwards the two diverge, and a card can advertise hundreds of members that resolve to nothing — with no way for a client to tell it from a healthy card without re-readinggraph.jsonitself. Graph-derived cards (kind=community|node) now also report how many members the current graph carries.0means the scope is dead: skip it rather than descend.
On one real 7,133-node graph this immediately surfaced 2 dead root cards and 42 partially-resolved ones that nobody had noticed.
- The
_CLIENT_FACTORYtest seam honours the caller's timeout.build_default_json_clientdropped it, handing the injected fake a 30 s default regardless — so a test pinning a timeout silently proved nothing. Seam-only; production composition was already correct.
Upgrading from v0.25.0
Drop-in, with one thing to know: extraction is now bounded by default (1800 s per attempt). If you deliberately want unbounded runs, set TESSERAE_EXTRACT_TIMEOUT=0.
The bound is per attempt, not per document — on timeout the client rotates to the next CODEX_HOME / claude config dir, so a document's worst case is timeout × configured profiles.
If a past compile left documents stuck on deterministic extraction, recover them with:
tesserae compile --changed-only --retry-fallbacks
Only documents whose typed extraction actually failed are re-attempted; clean ones stay skipped.