Skip to content

fix(transcript): degrade session_transcript when no kb resolves - #593

Merged
plind-junior merged 2 commits into
vouchdev:testfrom
atoz96:fix/session-transcript-degrade-without-kb
Jul 30, 2026
Merged

fix(transcript): degrade session_transcript when no kb resolves#593
plind-junior merged 2 commits into
vouchdev:testfrom
atoz96:fix/session-transcript-degrade-without-kb

Conversation

@atoz96

@atoz96 atoz96 commented Jul 29, 2026

Copy link
Copy Markdown

rebased onto c89ca20, which fixed the red test ci while this was open. that fix gave the test a temp kb; this is the remaining half — making the handler itself degrade when there is no kb at all — so the two are complementary rather than competing.

what is still broken

kb.session_transcript reads the raw jsonl from the agent's own directory (~/.claude/projects, $CODEX_HOME/sessions). the kb is consulted only to attach capture observations on the degraded path. but both agent surfaces resolve the store eagerly:

return transcript.load_transcript(_store(), session_id, agent=agent)

so calling it from a directory with no .vouch/ raises out of _store() and the caller gets internal_error — never the degraded envelope the tool docstring promises. an agent asking for a transcript from a scratch directory gets a server fault for a read that never needed the kb.

the change

load_transcript takes KBStore | None; _degraded returns an empty observation list when the store is absent. the leniency is opt-in through a _store_or_none() helper on the jsonl and mcp surfaces — everything that reads or writes knowledge keeps calling _store(), so a missing kb stays a hard error there. the cli keeps _load_store(): outside a kb that is a setup error a human should be told about, not degraded past.

tests

c89ca20's test now covers "kb present, raw transcript missing" and is left exactly as it landed. added alongside it:

  • test_handler_degrades_when_no_kb_resolves — the jsonl path with no kb at all
  • test_mcp_session_transcript_degrades_without_kb — the mcp surface, which had the same eager _store()
  • test_load_transcript_without_store_degrades — the store=None contract directly

verification

pytest tests/ -q --ignore=tests/embeddings   green
mypy src                                     Success: no issues found in 115 source files
ruff check src tests                         All checks passed!

happy to close this if you would rather kb.session_transcript keep requiring a kb like every other kb.* method — the behaviour question is yours, and the ci breakage that prompted it is already resolved.

Summary by CodeRabbit

  • Bug Fixes

    • Session transcript requests now degrade gracefully when knowledge-base data is unavailable, returning an unavailable result instead of an error.
    • Transcript responses safely return empty observations when no knowledge base can be resolved.
    • Codex transcript sessions now correctly report the model used when available.
  • Tests

    • Added coverage for transcript handling with missing data sources and unavailable knowledge bases.

@atoz96
atoz96 requested a review from plind-junior as a code owner July 29, 2026 17:02
@github-actions github-actions Bot added mcp mcp, jsonl, and http surfaces tests tests and fixtures size: S 50-199 changed non-doc lines labels Jul 29, 2026
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Transcript handlers now tolerate missing KB discovery by passing an optional store to transcript loading. Degraded responses omit observations when no store is available, and Codex transcript parsing now captures the session model from turn context records.

Changes

Transcript degradation

Layer / File(s) Summary
Optional KB resolution and handler wiring
src/vouch/jsonl_server.py, src/vouch/server.py
Both handlers add _store_or_none() and use it when loading session transcripts, allowing missing KB discovery to proceed without raising.
Nullable transcript loading and Codex normalization
src/vouch/transcript.py
load_transcript accepts KBStore | None; degraded results omit observations without a store, and Codex parsing fills the normalized model from turn_context.model.
Degradation tests
tests/test_session_transcript.py
Tests cover absent transcript data, unresolved KB configuration, nullable transcript loading, and MCP degradation responses.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

  • vouchdev/vouch#592: Updates the degraded transcript handler test to avoid KB discovery failures in CI.

Suggested reviewers: plind-junior

Sequence Diagram(s)

sequenceDiagram
  participant TranscriptHandler
  participant OptionalKBResolver
  participant load_transcript
  participant KBObservations
  TranscriptHandler->>OptionalKBResolver: Resolve KB store
  OptionalKBResolver-->>TranscriptHandler: KBStore or None
  TranscriptHandler->>load_transcript: Load session transcript
  load_transcript->>KBObservations: Read observations when store exists
  load_transcript-->>TranscriptHandler: Return available or degraded result
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: session_transcript now degrades when no KB resolves.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Fix failing CI checks
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/vouch/jsonl_server.py`:
- Around line 87-92: Convert the added prose to lowercase in the affected
docstrings/comments: `src/vouch/jsonl_server.py` lines 87-92 (`_store_or_none`),
`src/vouch/server.py` lines 73-78 and 701-702, and `src/vouch/transcript.py`
lines 367-371 and 374-386. Lowercase the specified sentence-initial words
without changing the surrounding content or behavior.

In `@tests/test_session_transcript.py`:
- Around line 199-208: Isolate transcript lookup roots in
test_handler_returns_degraded_when_absent and
test_mcp_session_transcript_degrades_without_kb so load_transcript(...,
agent=None) cannot discover real Claude or Codex sessions. Set
VOUCH_CLAUDE_PROJECTS_DIR and the Codex lookup-root environment variable to
empty temporary directories, or monkeypatch both lookup functions before
importing the handlers, while preserving the intended degraded responses.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0819a4a5-84ab-4606-96c5-581d707b2c45

📥 Commits

Reviewing files that changed from the base of the PR and between d033937 and a671f03.

📒 Files selected for processing (4)
  • src/vouch/jsonl_server.py
  • src/vouch/server.py
  • src/vouch/transcript.py
  • tests/test_session_transcript.py

Comment thread src/vouch/jsonl_server.py
Comment on lines +87 to +92
def _store_or_none() -> KBStore | None:
"""The KB when one resolves, else None.

Only for reads whose data source is outside `.vouch/` — the KB is an
enrichment, not the subject. Every method that reads or writes knowledge
must keep using `_store()` so a missing KB stays a hard error.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

keep added prose lowercase across the vouch sources.

The same path-level rule is violated in each changed docstring/comment:

  • src/vouch/jsonl_server.py#L87-L92: lowercase The, Only, and Every.
  • src/vouch/server.py#L73-L78: lowercase The, Only, and Every.
  • src/vouch/server.py#L701-L702: lowercase Degrades.
  • src/vouch/transcript.py#L367-L371: lowercase No and The.
  • src/vouch/transcript.py#L374-L386: lowercase Locate, Returns, and The.

As per path instructions, comments and review notes under src/vouch/** must use lowercase prose.

📍 Affects 3 files
  • src/vouch/jsonl_server.py#L87-L92 (this comment)
  • src/vouch/server.py#L73-L78
  • src/vouch/server.py#L701-L702
  • src/vouch/transcript.py#L367-L371
  • src/vouch/transcript.py#L374-L386
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vouch/jsonl_server.py` around lines 87 - 92, Convert the added prose to
lowercase in the affected docstrings/comments: `src/vouch/jsonl_server.py` lines
87-92 (`_store_or_none`), `src/vouch/server.py` lines 73-78 and 701-702, and
`src/vouch/transcript.py` lines 367-371 and 374-386. Lowercase the specified
sentence-initial words without changing the surrounding content or behavior.

Source: Path instructions

Comment thread tests/test_session_transcript.py
@atoz96
atoz96 force-pushed the fix/session-transcript-degrade-without-kb branch from a671f03 to 0222258 Compare July 29, 2026 17:12
the jsonl and mcp handlers resolved the store eagerly, so
kb.session_transcript raised instead of returning the degraded envelope
its own docstring promises when no .vouch/ was discoverable. the raw
transcript is read from the agent's own directory, not from the kb — the
kb only supplies capture observations for the fallback — so a missing kb
should cost the observations, not the whole call.

load_transcript now takes an optional store and _degraded returns an
empty observation list when it is absent. a _store_or_none() helper on
both surfaces makes that leniency opt-in: everything that reads or writes
knowledge keeps calling _store(), so a missing kb stays a hard error
there.

test_handler_returns_degraded_when_absent has been red on test since
9d7b37a stopped tracking the owner-local .vouch kb — it passed only
because that committed kb was always discoverable from the checkout. it
now chdirs to a tmp dir so it exercises the kb-absent path regardless of
ambient state, with the mcp surface and the store=None contract covered
alongside it.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/vouch/server.py`:
- Around line 80-83: Update the _store() flow to distinguish the expected
missing-KB condition from unrelated RuntimeError failures, using a dedicated
exception or sentinel for KBNotFoundError. Catch only that dedicated missing-KB
result when returning None, and allow RuntimeError from discover_root() or
KBStore(...) to propagate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 32e88d6c-bc85-43ba-b5ac-46048bc556fc

📥 Commits

Reviewing files that changed from the base of the PR and between a671f03 and 0222258.

📒 Files selected for processing (4)
  • src/vouch/jsonl_server.py
  • src/vouch/server.py
  • src/vouch/transcript.py
  • tests/test_session_transcript.py
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/vouch/transcript.py
  • tests/test_session_transcript.py
  • src/vouch/jsonl_server.py

Comment thread src/vouch/server.py
Comment on lines +80 to +83
try:
return _store()
except RuntimeError:
return None

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

preserve non-missing-kb failures

_store() converts KBNotFoundError into RuntimeError, so this catch also swallows any unrelated RuntimeError raised by discover_root() or KBStore(...). Malformed configuration or permission failures could therefore be reported as “no KB” and silently drop transcript enrichment. Use a dedicated missing-KB exception/sentinel and catch only that failure.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vouch/server.py` around lines 80 - 83, Update the _store() flow to
distinguish the expected missing-KB condition from unrelated RuntimeError
failures, using a dedicated exception or sentinel for KBNotFoundError. Catch
only that dedicated missing-KB result when returning None, and allow
RuntimeError from discover_root() or KBStore(...) to propagate.

@atoz96
atoz96 force-pushed the fix/session-transcript-degrade-without-kb branch from 0222258 to e89e1cb Compare July 29, 2026 17:17

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/vouch/jsonl_server.py (1)

87-97: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

preserve non-missing-kb failures

both helpers catch every RuntimeError, not only the expected missing-kb condition. unrelated failures from discover_root() or KBStore(...) can therefore be converted into a normal degraded success, hiding configuration or initialization problems. preserve a dedicated missing-kb exception/sentinel and catch only that value here.

proposed direction
-    except RuntimeError:
+    except MissingKBError:
         return None

update _store() to propagate the dedicated missing-kb exception instead of using the generic RuntimeError.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vouch/jsonl_server.py` around lines 87 - 97, Replace the generic
RuntimeError handling in _store_or_none() and its corresponding helper in
server.py with a dedicated missing-KB exception or sentinel. Update _store() to
propagate that specific missing-KB condition, and catch only it in both helpers
so discover_root() or KBStore initialization failures continue to propagate
normally. Apply the changes in src/vouch/jsonl_server.py lines 87-97 and
src/vouch/server.py lines 73-83.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_session_transcript.py`:
- Around line 240-244: Update test_load_transcript_without_store_degrades to
isolate both transcript lookup roots by setting CODEX_HOME to a separate empty
temporary directory alongside VOUCH_CLAUDE_PROJECTS_DIR before calling
transcript.load_transcript. Preserve the existing direct-load assertion and use
tmp_path for the isolated Codex root.

---

Duplicate comments:
In `@src/vouch/jsonl_server.py`:
- Around line 87-97: Replace the generic RuntimeError handling in
_store_or_none() and its corresponding helper in server.py with a dedicated
missing-KB exception or sentinel. Update _store() to propagate that specific
missing-KB condition, and catch only it in both helpers so discover_root() or
KBStore initialization failures continue to propagate normally. Apply the
changes in src/vouch/jsonl_server.py lines 87-97 and src/vouch/server.py lines
73-83.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7ffd2767-ca49-45a7-850a-83d2a1cdad31

📥 Commits

Reviewing files that changed from the base of the PR and between 0222258 and ab34ca0.

📒 Files selected for processing (4)
  • src/vouch/jsonl_server.py
  • src/vouch/server.py
  • src/vouch/transcript.py
  • tests/test_session_transcript.py

Comment on lines +240 to +244
def test_load_transcript_without_store_degrades(
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setenv("VOUCH_CLAUDE_PROJECTS_DIR", str(tmp_path / "projects"))
out = transcript.load_transcript(None, "11111111-1111-1111-1111-111111111111")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

isolate both transcript lookup roots in the direct-load test

with agent omitted, load_transcript() searches both claude and codex. this test sets only VOUCH_CLAUDE_PROJECTS_DIR, so an ambient codex rollout can make the assertion flaky. set CODEX_HOME to an empty temporary root as the neighboring tests do.

proposed fix
     monkeypatch.setenv("VOUCH_CLAUDE_PROJECTS_DIR", str(tmp_path / "projects"))
+    monkeypatch.setenv("CODEX_HOME", str(tmp_path / "codex"))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_session_transcript.py` around lines 240 - 244, Update
test_load_transcript_without_store_degrades to isolate both transcript lookup
roots by setting CODEX_HOME to a separate empty temporary directory alongside
VOUCH_CLAUDE_PROJECTS_DIR before calling transcript.load_transcript. Preserve
the existing direct-load assertion and use tmp_path for the isolated Codex root.

@plind-junior
plind-junior merged commit 8a3fb66 into vouchdev:test Jul 30, 2026
20 of 23 checks passed
plind-junior pushed a commit that referenced this pull request Jul 30, 2026
follow-up to the review on #593. _store_or_none caught the RuntimeError
that _store() wraps KBNotFoundError in, but that wrapper is
indistinguishable from any other RuntimeError out of discover_root() or
KBStore() — a malformed config.yaml or a permission failure was reported
as "no kb", silently dropping transcript enrichment instead of surfacing
a real fault.

both surfaces now call KBStore(discover_root()) and catch KBNotFoundError
directly, so only a genuinely absent kb degrades and everything else
propagates to the envelope as internal_error.

also from that review: the direct-load test isolated only the claude
lookup root, so an ambient codex rollout could satisfy the lookup and
flip the assertion — CODEX_HOME is now pointed at an empty dir like the
neighbouring tests do. and the prose i added in #593 is lowercased per
the src/vouch/** path instruction.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mcp mcp, jsonl, and http surfaces size: S 50-199 changed non-doc lines tests tests and fixtures

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants