Preserve renamed thread titles during reconciliation (#25624)

## Summary
- preserve existing explicit SQLite thread titles during rollout
reconciliation/backfill when the incoming rollout title is only
first-message-derived
- keep stale inferred-title repair behavior while avoiding session-index
scans during startup backfill
- add a regression test for renamed titles surviving reconcile

## Testing
- just fmt
- just test -p codex-rollout
- just test -p codex-state
This commit is contained in:
jif-oai authored and GitHub committed 2026-06-01 18:33:05 +02:00
1 parent f1d029cf75
commit 3cdce52865
5 files changed
+113 -2

No files matched your search

+94
View File
@@ -6,7 +6,13 @@ use chrono::DateTime;
use chrono::NaiveDateTime;
use chrono::Timelike;
use chrono::Utc;
use codex_protocol::protocol::EventMsg;
use codex_protocol::protocol::RolloutLine;
use codex_protocol::protocol::SessionMeta;
use codex_protocol::protocol::SessionMetaLine;
use codex_protocol::protocol::UserMessageEvent;
use pretty_assertions::assert_eq;
use std::path::Path;
use tempfile::TempDir;
#[test]
@@ -84,3 +90,91 @@ async fn try_init_times_out_waiting_for_stuck_startup_backfill() -> anyhow::Resu
Ok(())
}
#[tokio::test]
async fn reconcile_rollout_preserves_existing_explicit_title() -> anyhow::Result<()> {
let home = TempDir::new().expect("temp dir");
let thread_id = ThreadId::new();
let rollout_path = write_rollout_with_user_message(home.path(), thread_id, "Hey")?;
let runtime =
codex_state::StateRuntime::init(home.path().to_path_buf(), "test-provider".to_string())
.await?;
let mut metadata =
metadata::extract_metadata_from_rollout(rollout_path.as_path(), "test-provider")
.await?
.metadata;
assert_eq!(metadata.title, "Hey");
assert_eq!(metadata.first_user_message.as_deref(), Some("Hey"));
metadata.title = "math".to_string();
runtime.upsert_thread(&metadata).await?;
reconcile_rollout(
Some(runtime.as_ref()),
rollout_path.as_path(),
"test-provider",
/*builder*/ None,
&[],
/*archived_only*/ Some(false),
/*new_thread_memory_mode*/ None,
)
.await;
let persisted = runtime
.get_thread(thread_id)
.await?
.expect("thread should exist");
assert_eq!(persisted.title, "math");
assert_eq!(persisted.first_user_message.as_deref(), Some("Hey"));
Ok(())
}
fn write_rollout_with_user_message(
home: &Path,
thread_id: ThreadId,
message: &str,
) -> anyhow::Result<std::path::PathBuf> {
let dir = home.join("sessions/2026/06/01");
std::fs::create_dir_all(dir.as_path())?;
let path = dir.join(format!("rollout-2026-06-01T14-26-25-{thread_id}.jsonl"));
let lines = [
RolloutLine {
timestamp: "2026-06-01T14:26:25Z".to_string(),
item: RolloutItem::SessionMeta(SessionMetaLine {
meta: SessionMeta {
id: thread_id,
forked_from_id: None,
parent_thread_id: None,
timestamp: "2026-06-01T14:26:25Z".to_string(),
cwd: home.to_path_buf(),
originator: "test".to_string(),
cli_version: "test".to_string(),
source: SessionSource::Cli,
thread_source: None,
agent_nickname: None,
agent_role: None,
agent_path: None,
model_provider: Some("test-provider".to_string()),
base_instructions: None,
dynamic_tools: None,
memory_mode: None,
},
git: None,
}),
},
RolloutLine {
timestamp: "2026-06-01T14:26:26Z".to_string(),
item: RolloutItem::EventMsg(EventMsg::UserMessage(UserMessageEvent {
message: message.to_string(),
..Default::default()
})),
},
];
let jsonl = lines
.iter()
.map(serde_json::to_string)
.collect::<Result<Vec<_>, _>>()?
.join("\n");
std::fs::write(path.as_path(), format!("{jsonl}\n"))?;
Ok(path)
}