Files
Garry TanandClaude Fable 5 d45a5ddedc fix(gateway): keep length-stop truncation handling reachable after the #3217 contentless guard
The #3217 empty-completion guard throws AIConfigError for a contentless
'length' stop BEFORE callers can observe stopReason === 'length'. Two
callers had explicit truncation handling that the throw silently bypassed:

- facts/extract.ts: the #2113 retry-once-at-double-cap recovery never fired
  for a completion that spent the whole cap on internal reasoning (zero
  text) — the catch absorbed the throw as 'return []', silently losing
  facts for exactly the case the retry was built for.
- chronicle/extract-events.ts: the #2606 'truncated' failure marker
  degraded to a silent { events: [] } no-op for the same case.

Fix: chat() marks the contentless-length AIConfigError; the exported
isContentlessLengthError(err) predicate lets both callers branch on it
(no message matching). facts extract now routes the throw into the same
double-cap retry; chronicle preserves failure: 'truncated'.

Tests: end-to-end contentless-length retry through the REAL guard (via
the generateText transport, since the chat transport bypasses the guard),
plus predicate pins in gateway-empty-completion.test.ts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 11:23:22 -07:00
..