Skip to content

Commit 267c05f

Browse files
authored
fix: stabilize list_dir pagination order (#8826)
Sort list_dir entries before applying offset/limit so pagination matches the displayed order, update pagination/truncation expectations, and add coverage for sorted pagination. This ensures stable, predictable directory pages when list_dir is enabled.
1 parent 634650d commit 267c05f

File tree

1 file changed

+45
-5
lines changed

1 file changed

+45
-5
lines changed

codex-rs/core/src/tools/handlers/list_dir.rs

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@ async fn list_dir_slice(
125125
return Ok(Vec::new());
126126
}
127127

128+
entries.sort_unstable_by(|a, b| a.name.cmp(&b.name));
129+
128130
let start_index = offset - 1;
129131
if start_index >= entries.len() {
130132
return Err(FunctionCallError::RespondToModel(
@@ -135,11 +137,10 @@ async fn list_dir_slice(
135137
let remaining_entries = entries.len() - start_index;
136138
let capped_limit = limit.min(remaining_entries);
137139
let end_index = start_index + capped_limit;
138-
let mut selected_entries = entries[start_index..end_index].to_vec();
139-
selected_entries.sort_unstable_by(|a, b| a.name.cmp(&b.name));
140+
let selected_entries = &entries[start_index..end_index];
140141
let mut formatted = Vec::with_capacity(selected_entries.len());
141142

142-
for entry in &selected_entries {
143+
for entry in selected_entries {
143144
formatted.push(format_entry_line(entry));
144145
}
145146

@@ -273,6 +274,7 @@ impl From<&FileType> for DirEntryKind {
273274
#[cfg(test)]
274275
mod tests {
275276
use super::*;
277+
use pretty_assertions::assert_eq;
276278
use tempfile::tempdir;
277279

278280
#[tokio::test]
@@ -404,6 +406,44 @@ mod tests {
404406
);
405407
}
406408

409+
#[tokio::test]
410+
async fn paginates_in_sorted_order() {
411+
let temp = tempdir().expect("create tempdir");
412+
let dir_path = temp.path();
413+
414+
let dir_a = dir_path.join("a");
415+
let dir_b = dir_path.join("b");
416+
tokio::fs::create_dir(&dir_a).await.expect("create a");
417+
tokio::fs::create_dir(&dir_b).await.expect("create b");
418+
419+
tokio::fs::write(dir_a.join("a_child.txt"), b"a")
420+
.await
421+
.expect("write a child");
422+
tokio::fs::write(dir_b.join("b_child.txt"), b"b")
423+
.await
424+
.expect("write b child");
425+
426+
let first_page = list_dir_slice(dir_path, 1, 2, 2)
427+
.await
428+
.expect("list page one");
429+
assert_eq!(
430+
first_page,
431+
vec![
432+
"a/".to_string(),
433+
" a_child.txt".to_string(),
434+
"More than 2 entries found".to_string()
435+
]
436+
);
437+
438+
let second_page = list_dir_slice(dir_path, 3, 2, 2)
439+
.await
440+
.expect("list page two");
441+
assert_eq!(
442+
second_page,
443+
vec!["b/".to_string(), " b_child.txt".to_string()]
444+
);
445+
}
446+
407447
#[tokio::test]
408448
async fn handles_large_limit_without_overflow() {
409449
let temp = tempdir().expect("create tempdir");
@@ -450,7 +490,7 @@ mod tests {
450490
}
451491

452492
#[tokio::test]
453-
async fn bfs_truncation() -> anyhow::Result<()> {
493+
async fn truncation_respects_sorted_order() -> anyhow::Result<()> {
454494
let temp = tempdir()?;
455495
let dir_path = temp.path();
456496
let nested = dir_path.join("nested");
@@ -467,7 +507,7 @@ mod tests {
467507
vec![
468508
"nested/".to_string(),
469509
" child.txt".to_string(),
470-
"root.txt".to_string(),
510+
" deeper/".to_string(),
471511
"More than 3 entries found".to_string()
472512
]
473513
);

0 commit comments

Comments
 (0)