diff --git a/crates/freshell-freshagent/src/session_handoff/tests.rs b/crates/freshell-freshagent/src/session_handoff/tests.rs index 22b1d30ca..5dfe39b89 100644 --- a/crates/freshell-freshagent/src/session_handoff/tests.rs +++ b/crates/freshell-freshagent/src/session_handoff/tests.rs @@ -6291,20 +6291,29 @@ async fn the_claude_handoff_target_binding_carries_the_handoff_generation() { // The target resume's sidecar init adoption writes the binding row // (async to the runner's commit) — bounded-poll until it lands. + // DEFLAKE (wait-depth): the loop previously broke on ANY matching row, + // but an interim unfenced row (observed pair None) can land FIRST — + // the runner's under-ticket adoption (the row this test asserts on) + // follows it. Poll for the SETTLED row — one stamped with the supplied + // handoff pair — so the assertions below never read an interim row. + // The r27 regression shape (the adoption writing None and never the + // pair) still fails here at the deadline, with the full dump. let deadline = tokio::time::Instant::now() + Duration::from_secs(15); loop { - if fake - .bindings - .lock() - .unwrap() - .iter() - .any(|b| b.provider == "claude" && b.session_id == sid && b.mode == "freshclaude") - { + if fake.bindings.lock().unwrap().iter().any(|b| { + b.provider == "claude" + && b.session_id == sid + && b.mode == "freshclaude" + && b.observed_epoch == Some(rig.ownership.boot_epoch()) + && b.observed_generation == Some(committed_generation) + }) { break; } assert!( tokio::time::Instant::now() < deadline, - "the target resume's session-init adoption never wrote its binding row" + "the under-ticket adoption never stamped its binding row with the supplied \ + handoff pair — bindings: {:?}", + fake.bindings.lock().unwrap() ); tokio::time::sleep(Duration::from_millis(25)).await; } diff --git a/crates/freshell-platform/src/mcp_inject_tests.rs b/crates/freshell-platform/src/mcp_inject_tests.rs index bf6542b19..4e6d8f1b2 100644 --- a/crates/freshell-platform/src/mcp_inject_tests.rs +++ b/crates/freshell-platform/src/mcp_inject_tests.rs @@ -502,7 +502,19 @@ fn live_wslpath_timeout_falls_back_and_reaps_the_child() { "timed-out conversion should return promptly" ); - let pid = std::fs::read_to_string(&pid_file).expect("timeout script wrote its pid"); + // The child writes its pid as the script's FIRST statement, so when the + // file is absent at read time the shell NEVER STARTED: under heavy + // parallel load (the workspace gate; WSL fork latency) the conversion's + // 3s deadline can kill the child before its fork+exec ever runs. A + // pre-start kill+wait IS the reap — the property under test holds by + // construction, and the leak this test guards against (a started + // `sleep` surviving the timeout) always leaves the pid file behind for + // the kill -0 assertion below. (Base-gate flake: the old unconditional + // read panicked here on the pre-start-kill outcome.) + let pid = match std::fs::read_to_string(&pid_file) { + Ok(pid) => pid, + Err(_) => return, + }; let status = std::process::Command::new("kill") .arg("-0") .arg(pid.trim())