fix(transcript): degrade session_transcript when no kb resolves - #593
Conversation
WalkthroughTranscript 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. ChangesTranscript degradation
Estimated code review effort: 2 (Simple) | ~10 minutes Possibly related PRs
Suggested reviewers: 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
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (4)
src/vouch/jsonl_server.pysrc/vouch/server.pysrc/vouch/transcript.pytests/test_session_transcript.py
| 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. |
There was a problem hiding this comment.
📐 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: lowercaseThe,Only, andEvery.src/vouch/server.py#L73-L78: lowercaseThe,Only, andEvery.src/vouch/server.py#L701-L702: lowercaseDegrades.src/vouch/transcript.py#L367-L371: lowercaseNoandThe.src/vouch/transcript.py#L374-L386: lowercaseLocate,Returns, andThe.
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-L78src/vouch/server.py#L701-L702src/vouch/transcript.py#L367-L371src/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
a671f03 to
0222258
Compare
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.
There was a problem hiding this comment.
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
📒 Files selected for processing (4)
src/vouch/jsonl_server.pysrc/vouch/server.pysrc/vouch/transcript.pytests/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
| try: | ||
| return _store() | ||
| except RuntimeError: | ||
| return None |
There was a problem hiding this comment.
🩺 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.
0222258 to
e89e1cb
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
src/vouch/jsonl_server.py (1)
87-97: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick winpreserve non-missing-kb failures
both helpers catch every
RuntimeError, not only the expected missing-kb condition. unrelated failures fromdiscover_root()orKBStore(...)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 Noneupdate
_store()to propagate the dedicated missing-kb exception instead of using the genericRuntimeError.🤖 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
📒 Files selected for processing (4)
src/vouch/jsonl_server.pysrc/vouch/server.pysrc/vouch/transcript.pytests/test_session_transcript.py
| 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") |
There was a problem hiding this comment.
🩺 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.
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.
rebased onto c89ca20, which fixed the red
testci 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_transcriptreads 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:so calling it from a directory with no
.vouch/raises out of_store()and the caller getsinternal_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_transcripttakesKBStore | None;_degradedreturns 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 alltest_mcp_session_transcript_degrades_without_kb— the mcp surface, which had the same eager_store()test_load_transcript_without_store_degrades— thestore=Nonecontract directlyverification
happy to close this if you would rather
kb.session_transcriptkeep requiring a kb like every otherkb.*method — the behaviour question is yours, and the ci breakage that prompted it is already resolved.Summary by CodeRabbit
Bug Fixes
Tests