[codex-backend] Prefer sqlite git info for rollout-path reads (#20228)

### Summary

- Path-based local thread reads currently return rollout/session git
metadata directly, so `thread/resume` can disagree with persisted SQLite
metadata for the same thread.
- Merge non-null SQLite git fields over rollout-path reads while keeping
rollout values as fallbacks for fields SQLite does not know.
- Add focused regression coverage for rollout-path reads so persisted
branch updates are preserved during resume.

### Testing

- `cargo test -p codex-thread-store`
This commit is contained in:
joeytrasatti-openai
2026-04-29 10:54:37 -07:00
committed by GitHub
parent d0204c3dcc
commit 47fba5df4a

View File

@@ -82,6 +82,22 @@ pub(super) async fn read_thread_by_rollout_path(
message: format!("thread {} is archived", thread.thread_id),
});
}
if let Some(metadata) = read_sqlite_metadata(store, thread.thread_id).await {
let existing_git_info = thread.git_info.take();
let (fallback_sha, fallback_branch, fallback_origin_url) = match existing_git_info {
Some(info) => (
info.commit_hash.map(|sha| sha.0),
info.branch,
info.repository_url,
),
None => (None, None, None),
};
thread.git_info = git_info_from_parts(
metadata.git_sha.or(fallback_sha),
metadata.git_branch.or(fallback_branch),
metadata.git_origin_url.or(fallback_origin_url),
);
}
attach_history_if_requested(&mut thread, include_history).await?;
Ok(thread)
}
@@ -433,6 +449,55 @@ mod tests {
assert_eq!(thread.preview, "Hello from user");
}
#[tokio::test]
async fn read_thread_by_rollout_path_prefers_sqlite_git_info() {
let home = TempDir::new().expect("temp dir");
let config = test_config(home.path());
let store = LocalThreadStore::new(config.clone());
let uuid = Uuid::from_u128(223);
let thread_id = ThreadId::from_string(&uuid.to_string()).expect("valid thread id");
let active_path =
write_session_file(home.path(), "2025-01-03T12-00-00", uuid).expect("session file");
let runtime = codex_state::StateRuntime::init(
config.sqlite_home.clone(),
config.model_provider_id.clone(),
)
.await
.expect("state db should initialize");
let mut builder = ThreadMetadataBuilder::new(
thread_id,
active_path.clone(),
Utc::now(),
SessionSource::Cli,
);
builder.model_provider = Some(config.model_provider_id.clone());
builder.git_branch = Some("sqlite-branch".to_string());
runtime
.upsert_thread(&builder.build(config.model_provider_id.as_str()))
.await
.expect("state db upsert should succeed");
let thread = store
.read_thread_by_rollout_path(
active_path,
/*include_archived*/ false,
/*include_history*/ false,
)
.await
.expect("read thread by rollout path");
let git_info = thread.git_info.expect("git info should be present");
assert_eq!(git_info.branch.as_deref(), Some("sqlite-branch"));
assert_eq!(
git_info.commit_hash.as_ref().map(|sha| sha.0.as_str()),
Some("abcdef")
);
assert_eq!(
git_info.repository_url.as_deref(),
Some("https://example.com/repo.git")
);
}
#[tokio::test]
async fn read_thread_returns_archived_rollout_when_requested() {
let home = TempDir::new().expect("temp dir");