From d8e954f48f5ca70ed37ab66afe73c65261b9b28d Mon Sep 17 00:00:00 2001 From: Yaroslav98214 Date: Thu, 16 Jul 2026 02:21:15 +0000 Subject: [PATCH] fix(transcript): record the codex session model from turn_context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit parse_codex_transcript only read session_meta, which carries cwd, branch and timestamps but not the model — codex puts the turn model on the turn_context record. so the normalized transcript's session model was always none for codex, while the claude parser fills it from the assistant message. read the model off turn_context (first non-empty wins) so both agents expose it. --- src/vouch/transcript.py | 10 ++++++++++ tests/test_session_transcript.py | 4 ++++ 2 files changed, 14 insertions(+) diff --git a/src/vouch/transcript.py b/src/vouch/transcript.py index 5ca10d34..2b05314f 100644 --- a/src/vouch/transcript.py +++ b/src/vouch/transcript.py @@ -312,6 +312,16 @@ def flush() -> None: session["git_branch"] = git["branch"] continue + # codex records the turn's model on `turn_context`, not on + # `session_meta`, so without this the normalized transcript's model + # is always none for codex while the claude parser fills it. first + # non-empty wins (the model can change across turns; keep the first). + if rtype == "turn_context": + model = payload.get("model") + if isinstance(model, str) and model.strip() and session["model"] is None: + session["model"] = model.strip() + continue + if rtype != "response_item": continue if len(messages) >= max_messages: diff --git a/tests/test_session_transcript.py b/tests/test_session_transcript.py index 19c0a6d9..1581f443 100644 --- a/tests/test_session_transcript.py +++ b/tests/test_session_transcript.py @@ -213,6 +213,8 @@ def test_handler_returns_degraded_when_absent() -> None: {"type": "session_meta", "payload": { "id": "cx-1", "cwd": "/repo", "timestamp": "2026-06-22T08:01:54Z", "git": {"branch": "feat/x"}}}, + {"type": "turn_context", "payload": { + "turn_id": "t1", "cwd": "/repo", "model": "gpt-5-codex"}}, {"type": "response_item", "payload": { "type": "message", "role": "developer", "content": [{"type": "input_text", "text": "boilerplate"}]}}, @@ -245,6 +247,8 @@ def test_parse_codex_pairs_calls_and_skips_boilerplate(tmp_path: Path) -> None: assert out["session"]["agent"] == "codex" assert out["session"]["cwd"] == "/repo" assert out["session"]["git_branch"] == "feat/x" + # codex carries the model on turn_context, not session_meta + assert out["session"]["model"] == "gpt-5-codex" roles = [m["role"] for m in out["messages"]] assert roles == ["user", "assistant"] # developer message skipped