Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions bt-daemon/src/translate/codex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,7 @@ impl CodexTranslator {
"session_meta" => self.open_root(scope, &payload, ts, ops),
"turn_context" => {
if let Some(m) = str_field(&payload, "model") {
let model_turn_id = str_field(&payload, "turn_id");
scope.model = Some(m.clone());
if scope.root_created {
let input = if scope.kind == ScopeKind::Main {
Expand All @@ -492,6 +493,16 @@ impl CodexTranslator {
..Default::default()
}));
}
if let Some(turn) = model_turn_id.as_deref().and_then(|turn_id| {
scope.open_turns.iter().find(|turn| turn.turn_id == turn_id)
}) {
Comment on lines +496 to +498

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Backfill the active turn when turn_id is absent

For rollout variants whose turn_context payload contains model but no turn_id—the format still used by the other Codex translator and replay fixtures—this expression always returns None. If such a context follows task_started, the already-inserted turn therefore retains a null model, so the change does not fix the first-turn trace for those sessions. Fall back to the sole/latest open turn when the context lacks an explicit ID.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

hmmm I think I'd rather not fill this in the ambiguous case - seems like that could get you into a weird state if there are somehow overlapping turns? From what I can see turn_id is never missing from these spans, and it looks like it should always get populated on the existing code path.

ops.push(SpanOp::Merge(SpanRow {
span_id: turn.span_id.clone(),
root_span_id: self.root_span_id.clone(),
metadata: Some(json!({ "model": m })),
..Default::default()
}));
}
}
}
"event_msg" => {
Expand Down
11 changes: 8 additions & 3 deletions bt-daemon/tests/codex_translator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ fn write_transcript(path: &std::path::Path) {
let records = vec![
json!({ "timestamp": "2026-01-01T00:00:01Z", "type": "session_meta",
"payload": { "id": "session-1", "cwd": "/whatever/myapp", "cli_version": "1.2.3" } }),
json!({ "timestamp": "2026-01-01T00:00:02Z", "type": "turn_context",
"payload": { "model": "gpt-5.5" } }),
json!({ "timestamp": "2026-01-01T00:00:03Z", "type": "event_msg",
json!({ "timestamp": "2026-01-01T00:00:02Z", "type": "event_msg",
"payload": { "type": "task_started", "turn_id": "t1" } }),
json!({ "timestamp": "2026-01-01T00:00:03Z", "type": "turn_context",
"payload": { "turn_id": "t1", "model": "gpt-5.5" } }),
json!({ "timestamp": "2026-01-01T00:00:04Z", "type": "event_msg",
"payload": { "type": "user_message", "message": "list the files" } }),
json!({ "timestamp": "2026-01-01T00:00:05Z", "type": "response_item",
Expand Down Expand Up @@ -191,6 +191,11 @@ fn codex_happy_path_builds_session_turn_llm_tool_tree() {
assert_eq!(turn.parent_span_ids, vec![root.span_id.clone()]);
assert_eq!(turn.input, Some(json!("list the files")));
assert_eq!(turn.output, Some(json!("Here are the files.")));
assert_eq!(
turn.metadata.as_ref().unwrap()["model"],
json!("gpt-5.5"),
"model backfilled from turn_context"
);
assert!(
turn.end_ms.is_some(),
"turn should be closed by task_complete"
Expand Down
Loading