mirror of
https://github.com/openai/codex.git
synced 2026-11-12 21:20:34 +00:00
Fix pagination for unindexed threads sorted by ascending recency (#61181)
## Why Listing threads by ascending `recency_at` can omit `next_cursor` when rollout files have not yet been indexed in SQLite, preventing clients from fetching the remaining threads. ## What changed Scan and index one extra rollout for ascending recency listings when SQLite is available, so the database can detect another page. Trim filesystem results to the requested page size for this sort order. ## Testing Add a regression test that creates two unindexed rollouts after startup backfill and lists them with a provider filter and a page size of one. Verify both threads are returned in order, with a cursor only on the first page. GitOrigin-RevId: 09354055b6e60666a493d9411ed3eb9778b73c0a
This commit is contained in:
@@ -2617,6 +2617,52 @@ async fn thread_list_sort_recency_at_uses_state_db_order_with_provider_filter()
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_list_cold_ascending_recency_returns_next_cursor() -> Result<()> {
|
||||
let home = TempDir::new()?;
|
||||
create_minimal_config(home.path())?;
|
||||
let mut server = init_mcp(home.path()).await?;
|
||||
// Finish startup backfill before adding the two unindexed rollouts.
|
||||
list_threads(
|
||||
&mut server,
|
||||
/*cursor*/ None,
|
||||
Some(1),
|
||||
/*providers*/ None,
|
||||
/*source_kinds*/ None,
|
||||
/*archived*/ None,
|
||||
)
|
||||
.await?;
|
||||
let ids = create_fake_rollouts(
|
||||
home.path(),
|
||||
/*count*/ 2,
|
||||
|_| "mock_provider",
|
||||
|i| {
|
||||
timestamp_at(
|
||||
/*year*/ 2025, /*month*/ 1, /*day*/ 1, /*hour*/ 10, i as u32,
|
||||
/*second*/ 0,
|
||||
)
|
||||
},
|
||||
"Cold thread",
|
||||
)?;
|
||||
|
||||
let mut params: codex_app_server_protocol::ThreadListParams = serde_json::from_value(json!({
|
||||
"limit": 1, "sortKey": "recency_at", "sortDirection": "asc", "modelProviders": ["mock_provider"]
|
||||
}))?;
|
||||
for (index, id) in ids.into_iter().enumerate() {
|
||||
let page: ThreadListResponse = server
|
||||
.request(|request_id| ClientRequest::ThreadList {
|
||||
request_id,
|
||||
params: params.clone(),
|
||||
})
|
||||
.await?;
|
||||
let page_ids: Vec<_> = page.data.into_iter().map(|thread| thread.id).collect();
|
||||
assert_eq!(page_ids, [id]);
|
||||
assert_eq!(page.next_cursor.is_some(), index == 0);
|
||||
params.cursor = page.next_cursor;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn thread_list_updated_at_paginates_with_cursor() -> Result<()> {
|
||||
let codex_home = TempDir::new()?;
|
||||
|
||||
@@ -612,9 +612,15 @@ impl RolloutRecorder {
|
||||
};
|
||||
let fs_page = match sort_direction {
|
||||
SortDirection::Asc => {
|
||||
// SQLite needs one extra indexed row to produce a recency cursor.
|
||||
let scan_size = if sort_key == ThreadSortKey::RecencyAt && state_db_ctx.is_some() {
|
||||
page_size.saturating_add(1)
|
||||
} else {
|
||||
page_size
|
||||
};
|
||||
list_threads_from_files_asc(
|
||||
codex_home,
|
||||
page_size,
|
||||
scan_size,
|
||||
cursor,
|
||||
sort_key,
|
||||
file_config,
|
||||
@@ -1377,8 +1383,8 @@ fn page_from_filesystem_scan(
|
||||
sort_key: ThreadSortKey,
|
||||
) -> ThreadsPage {
|
||||
match sort_direction {
|
||||
SortDirection::Asc => page,
|
||||
SortDirection::Desc => truncate_fs_page(page, page_size, sort_key),
|
||||
SortDirection::Asc if sort_key != ThreadSortKey::RecencyAt => page,
|
||||
SortDirection::Asc | SortDirection::Desc => truncate_fs_page(page, page_size, sort_key),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user