Skip to content

fix(llmxrr): refuse to record failed exchanges; heal stored failures - #31

Merged
jadb merged 1 commit into
mainfrom
fix/xrr-refuse-failures
Oct 1, 2026
Merged

jadb merged 1 commit into
mainfrom
fix/xrr-refuse-failures

Conversation

@jadb

@jadb jadb commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

Recording no longer saves failed exchanges as cassettes, and a stored failure heals under -update.

Bug

xrr's FileSession.record saves whatever do() returns: a transport error as an error: envelope, a non-2xx as a payload. Record mode replays first, so a stored failure was never a miss and -update never retried it.

Fix (internal/llmxrr.Transport, covers in-process tests and the -tags xrr binary seam)

  • Record mode makes the live call itself and checks it before xrr sees it: transport error or non-2xx → ErrRefused, nothing written. Message names method, cassette path (no host, key= stripped), status and body start; keys, Bearer tokens and RecordingKeys masked; URL dropped from a wrapped *url.Error.
  • After one refusal, later misses in the run fail with the same refusal and never dial (SDK retries + non-streaming fallback were each going live).
  • Under -update, a stored failure counts as missing, is sent again, and the success overwrites it.
  • Plain replay of a stored failure fails with ErrStoredFailure ("re-record it"); a following miss leads with that cause.
  • Journal (Exchange.Refused, Exchange.Error) and harnesses report these before misses.
  • Same check for the internal/llm catalog cassette.
  • TestCommittedCassettes scans every committed cassette dir for key shapes and stored failures on a plain go test.

xrr alpha.5 has no record filter or pre-save hook, so the check lives in foo's transport.

Tests

internal/llmxrr (200 saved; 400/500/closed port refused with nothing on disk and one provider hit; no secrets; heal; stored-failure replay error), internal/llm (503 refused), e2e xrr_record_test.go on the real -tags xrr binary. Local httptest servers only.

Docs

CONTRIBUTING.md: refusal, heal, catalog cassette row.

xrr record mode saves whatever do() returns, errors included; no hook
to skip a save. Transport error or 4xx/5xx landed in cassettes, then
replayed forever; replay-first -update never retried it.

- record mode: live call judged before session save; transport error
  or non-2xx -> ErrRefused (method, cassette path, status, masked body
  snippet), nothing written
- refusal voids rest of recording: later misses fail same way, no
  dialing; SDK retries + stream fallback spend no live calls
- replay-first record: stored failure (error envelope / non-2xx)
  treated as missing, re-recorded, overwritten
- plain replay: stored failure -> ErrStoredFailure, never handed back
  as provider reply; later misses lead with that cause
- passthrough unchanged
- Exchange gains Refused + Error; e2e + tool-round harnesses report
  them before misses
- CheckRecordable, Redact, CheckNoFailures exported; llm catalog
  cassette gated the same way
- guard test: every committed testdata/cassettes dir scanned for key
  shapes + stored failures on plain go test
- e2e: foo -tags xrr binary against local 200/400/500/closed-port
  stand-ins; refusal, no files, single provider hit, heal under record
- CONTRIBUTING: refusal + heal semantics, catalog cassette row
@jadb
jadb merged commit a18c61b into main Oct 1, 2026
2 checks passed
@jadb
jadb deleted the fix/xrr-refuse-failures branch October 1, 2026 07:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant