From 09947213fe0bf1adde7fdbd2697554ed539e20e1 Mon Sep 17 00:00:00 2001 From: houseme Date: Sun, 6 Sep 2026 02:08:30 +0800 Subject: [PATCH] fix(heal): preserve compatible listing EOF outcomes Keep truncated heal listings without continuation tokens as complete compatibility EOFs and assert the canonical task outcome. Co-Authored-By: heihutu Co-Authored-By: zhi22915 --- crates/heal/src/heal/task/heal_bucket.rs | 2 +- crates/heal/src/heal/task/tests.rs | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/heal/src/heal/task/heal_bucket.rs b/crates/heal/src/heal/task/heal_bucket.rs index ae8c2891d..c6225c844 100644 --- a/crates/heal/src/heal/task/heal_bucket.rs +++ b/crates/heal/src/heal/task/heal_bucket.rs @@ -554,7 +554,7 @@ impl HealTask { continuation_token = next_heal_listing_token(bucket, prefix, next_token, is_truncated)?; if continuation_token.is_none() { - self.outcome.write().await.mark_untraversable(); + // Truncated without a continuation token is a compatibility EOF. break; } } diff --git a/crates/heal/src/heal/task/tests.rs b/crates/heal/src/heal/task/tests.rs index 3d354a8c6..ba842ab8b 100644 --- a/crates/heal/src/heal/task/tests.rs +++ b/crates/heal/src/heal/task/tests.rs @@ -1778,6 +1778,8 @@ async fn test_recursive_bucket_heal_skips_object_dir_candidates() { #[tokio::test] async fn test_recursive_bucket_heal_treats_missing_continuation_token_as_end() { + use crate::heal::outcome::{HealExecutionOutcome, HealTraversalCoverage}; + // A version listing can report the final page as truncated with no // continuation token. That is treated as end-of-listing (not an error), // so the returned page is healed and the pass terminates cleanly instead @@ -1799,10 +1801,16 @@ async fn test_recursive_bucket_heal_treats_missing_continuation_token_as_end() { ); let task = HealTask::from_request(request, storage.clone()); - task.heal_bucket("bucket-a") + task.execute() .await .expect("truncated-without-token must terminate cleanly, not loop or error"); + assert_eq!(task.get_status().await, HealTaskStatus::Completed); + let outcome = task.get_outcome().await; + assert_eq!(outcome.execution, HealExecutionOutcome::Completed); + assert_eq!(outcome.coverage, HealTraversalCoverage::Complete); + assert_eq!(outcome.counters.processed, 1); + assert_eq!( storage.healed_objects.lock().unwrap().as_slice(), ["object-a".to_string()],