4.2 KB · updated 2026-07-31 · md

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-group killpg was correctly wired the whole time; it simply never fired, because communicate(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 in communicate() 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-fallbacks recovers 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-only skipped it on every later compile and the degradation was permanent until the file's own content changed. Recovering a handful of docs meant hand-editing manifest.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, mirroring distill --retry-fallbacks.
  • live_member_count on graph_map cards. size and leaf_member_count both come from the hierarchy.json sidecar, which is written mid-compile. When graph.json is 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-reading graph.json itself. Graph-derived cards (kind=community|node) now also report how many members the current graph carries. 0 means 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_FACTORY test seam honours the caller's timeout. build_default_json_client dropped 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.