diff --git a/crates/freshell-ws/tests/codex_locator_activity.rs b/crates/freshell-ws/tests/codex_locator_activity.rs index 3a677ee85..17a34e544 100644 --- a/crates/freshell-ws/tests/codex_locator_activity.rs +++ b/crates/freshell-ws/tests/codex_locator_activity.rs @@ -82,22 +82,22 @@ async fn send_create(ws: &mut common::TestWs, mode: &str) -> String { .to_string() } -/// Scan WS text frames until `pred` matches or the 30s budget elapses. +/// Scan WS text frames until `pred` matches or the 120s budget elapses. /// Non-matching frames are simply skipped (no drop-on-mismatch semantics). /// -/// DEFLAKE (f3wp refresh): was 10s. Under workspace-level load (full -/// cargo-test parallelism alongside a parallel playwright e2e run) the -/// inotify-driven rollout read plus frame delivery was observed to exceed -/// 10s once (`/tmp/f3wp-refresh/cargo-runverify1.log`, "expected -/// terminal.turn.complete ... stamped by the locator adoption", -/// 15.43s total test time). The assertions are unchanged -- only the wait -/// budget grew; a genuinely missing frame still fails, 20s later. +/// DEFLAKE (f3wp refresh → pkvz): was 10s → 30s. Under workspace-level load +/// (full cargo-test parallelism on a 4-core CI runner) the inotify-driven +/// rollout read plus the locator adoption sweep latency was observed to +/// exceed 30s (kata pkvz, 3+ CI occurrences). Bumped to 120s to cover the +/// observed sweep latency under contention with margin. The assertions are +/// unchanged — only the wait budget grew; a genuinely missing frame still +/// fails, 120s later. #[cfg(unix)] async fn wait_for_frame( ws: &mut common::TestWs, pred: impl Fn(&serde_json::Value) -> bool, ) -> bool { - let deadline = tokio::time::Instant::now() + Duration::from_secs(30); + let deadline = tokio::time::Instant::now() + Duration::from_secs(120); while tokio::time::Instant::now() < deadline { let remaining = deadline.saturating_duration_since(tokio::time::Instant::now()); match tokio::time::timeout(remaining.max(Duration::from_millis(1)), ws.next()).await { diff --git a/crates/freshell-ws/tests/pane_reconcile_freshagent.rs b/crates/freshell-ws/tests/pane_reconcile_freshagent.rs index aee055a6e..53c4c35f9 100644 --- a/crates/freshell-ws/tests/pane_reconcile_freshagent.rs +++ b/crates/freshell-ws/tests/pane_reconcile_freshagent.rs @@ -871,15 +871,33 @@ async fn dead_session_verdict_is_warn_logged_with_claimed_identity() { assert_eq!(verdicts[0]["verdict"], "dead_session"); assert_eq!(verdicts[0]["reason"], "session_not_on_disk"); - let events = events.lock().expect("capture lock"); - let hits: Vec<&CapturedEvent> = events - .iter() - .filter(|e| e.message.contains("pane_reconcile.dead_session")) - .collect(); + // The WARN is emitted before the response frame is sent, but under CI + // contention the tracing layer's push can lag the client's read by a + // scheduling quantum. Bounded-poll the capture instead of asserting + // immediately so the test is resilient to that lag without masking a + // genuinely missing log (a 5s budget on a current-thread runtime). + let hits = tokio::time::timeout(std::time::Duration::from_secs(5), async { + loop { + let hits: Vec = { + let events = events.lock().expect("capture lock"); + events + .iter() + .filter(|e| e.message.contains("pane_reconcile.dead_session")) + .cloned() + .collect() + }; + if hits.len() == 1 { + return hits; + } + tokio::time::sleep(std::time::Duration::from_millis(50)).await; + } + }) + .await + .expect("dead_session WARN was not captured within 5s"); assert_eq!( hits.len(), 1, - "exactly one dead_session WARN per dead verdict; got {events:?}" + "exactly one dead_session WARN per dead verdict; got {hits:?}" ); let fields = &hits[0].fields; assert!(