From 7d317701ef4665dbbc4d58e52bd26cc14209bfcd Mon Sep 17 00:00:00 2001 From: ualtinok <94532+ualtinok@users.noreply.github.com> Date: Wed, 28 Oct 2026 04:30:32 +0100 Subject: [PATCH 1/2] mason: retry post-replay watch scans instead of asserting a single pass Co-authored-by: alfonso-aft <289616620+alfonso-aft@users.noreply.github.com> (cherry picked from commit 1f6722d6ab12bd99aedc676044659fc4357c5e52) --- crates/aft/src/bash_background/registry.rs | 39 +++++++++++++++++++--- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/crates/aft/src/bash_background/registry.rs b/crates/aft/src/bash_background/registry.rs index 5007ba524..55c2b1775 100644 --- a/crates/aft/src/bash_background/registry.rs +++ b/crates/aft/src/bash_background/registry.rs @@ -27020,6 +27020,39 @@ mod tests { .unwrap(); } + /// Scan until a replayed watch delivers its pending match. + /// + /// A scan that cannot open an artifact (or loses a lock race) returns + /// without emitting and without advancing cursors, so production retries + /// the same bytes on a later pass; a test that scans exactly once turns + /// that transient skip into a false negative. Retry briefly: a watch + /// that never delivers still fails, with the scan state attached. + fn scan_until_new_match( + live: &BgTaskRegistry, + live_task: &Arc, + live_frames: &Mutex>, + ) -> BashPatternMatchFrame { + let seen = pattern_match_frames(live_frames).len(); + let deadline = std::time::Instant::now() + std::time::Duration::from_secs(10); + loop { + live.scan_task_watch_output(live_task); + let mut current = pattern_match_frames(live_frames); + if current.len() > seen { + return current.pop().unwrap(); + } + if std::time::Instant::now() >= deadline { + panic!( + "replayed watch delivered no match: active={} running={} cursors={:?} frames={}", + live.active_watch_count(&live_task.task_id), + live_task.is_running(), + live.watch_stream_cursors(&live_task.task_id), + pattern_match_frames(live_frames).len(), + ); + } + std::thread::sleep(std::time::Duration::from_millis(50)); + } + } + #[test] fn each_line_once_warning_stays_armed_through_ack_and_restart() { for restart in [false, true] { @@ -27089,8 +27122,7 @@ mod tests { let _reap_live = KillTasksOnDrop(live.clone()); append_each_line_output(&task.paths.stdout, b"READY after warning\n"); let live_task = live.task_for_session(&task.task_id, "session").unwrap(); - live.scan_task_watch_output(&live_task); - let matched = pattern_match_frames(&live_frames).pop().unwrap(); + let matched = scan_until_new_match(&live, &live_task, &live_frames); assert_eq!( matched.batch.as_ref().unwrap().lines[0].text, "READY after warning" @@ -27303,8 +27335,7 @@ mod tests { } let replayed_task = replayed.task_for_session(&task.task_id, "session").unwrap(); append_each_line_output(&task.paths.stderr, b"READY b\n"); - replayed.scan_task_watch_output(&replayed_task); - let newest = pattern_match_frames(&replay_frames).pop().unwrap(); + let newest = scan_until_new_match(&replayed, &replayed_task, &replay_frames); assert!( newest.match_offset > first.match_offset, "acknowledged first: {acknowledge_first}" From 13cf4c93a5e486bd1b08836b21170b71ecc04370 Mon Sep 17 00:00:00 2001 From: ualtinok <94532+ualtinok@users.noreply.github.com> Date: Wed, 28 Oct 2026 04:53:01 +0100 Subject: [PATCH 2/2] Flake batch 13: accurate retry comment and search-quality descriptor Co-authored-by: alfonso-aft <289616620+alfonso-aft@users.noreply.github.com> --- .../slice-descriptors/land-flake-batch-13.json | 12 ++++++++++++ crates/aft/src/bash_background/registry.rs | 11 ++++++----- 2 files changed, 18 insertions(+), 5 deletions(-) create mode 100644 benchmarks/aft-search/slice-descriptors/land-flake-batch-13.json diff --git a/benchmarks/aft-search/slice-descriptors/land-flake-batch-13.json b/benchmarks/aft-search/slice-descriptors/land-flake-batch-13.json new file mode 100644 index 000000000..51d107229 --- /dev/null +++ b/benchmarks/aft-search/slice-descriptors/land-flake-batch-13.json @@ -0,0 +1,12 @@ +{ + "slice_class": "non_ranking", + "targeted_mechanism": "none", + "kind": "harness", + "fixtures": [ + "harness-goldens" + ], + "slices": [ + "flake-batch-13" + ], + "note": "Test-only: two bash_watch restart tests retry the post-replay scan briefly. No changed path matches RANKING_FENCE_PREFIXES." +} diff --git a/crates/aft/src/bash_background/registry.rs b/crates/aft/src/bash_background/registry.rs index 55c2b1775..5416f23ad 100644 --- a/crates/aft/src/bash_background/registry.rs +++ b/crates/aft/src/bash_background/registry.rs @@ -27022,11 +27022,12 @@ mod tests { /// Scan until a replayed watch delivers its pending match. /// - /// A scan that cannot open an artifact (or loses a lock race) returns - /// without emitting and without advancing cursors, so production retries - /// the same bytes on a later pass; a test that scans exactly once turns - /// that transient skip into a false negative. Retry briefly: a watch - /// that never delivers still fails, with the scan state attached. + /// Right after a replay a scan can return without emitting and without + /// advancing the cursors (its early returns never persist a cursor), and + /// production's watchdog then rescans the same bytes on its next pass. A + /// test that scans exactly once turns that delay into a false negative. + /// Retry briefly: a watch that never delivers still fails, with the scan + /// state attached. fn scan_until_new_match( live: &BgTaskRegistry, live_task: &Arc,