From 0ed48dd24cba5e88514a7271ff45f0145f0cbd7d Mon Sep 17 00:00:00 2001 From: t Date: Sat, 19 Sep 2026 19:47:05 -0700 Subject: [PATCH 1/2] test(mcp-inject): the pre-start kill of a timed-out conversion IS the reap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The timeout test read the child's pid file unconditionally after the conversion's 3s-deadline fallback — but under heavy parallel load (the workspace gate; WSL fork latency) the deadline can kill the child BEFORE its fork+exec ever runs, so the pid file legitimately never exists and the read panicked (the third origin/main base-gate flake, 2026-09-19 rust phase, mcp_inject_tests.rs:505). The pid file is written as the script's FIRST statement, so its absence at read time deterministically means the child never started: a pre-start kill+wait IS the reap (the property under test holds by construction), while the leak this test guards against (a started sleep surviving the timeout) always leaves the file behind for the kill -0 assertion. The Err arm returns as a valid outcome instead of panicking. Mutation pair: with the conversion deadline forced to zero (the deterministic pre-start kill), the old test panics at :505 — the exact gate failure — and the fixed test passes; under the real 3s deadline the normal timeout+reap+kill-0 path is unchanged (3.01s). --- crates/freshell-platform/src/mcp_inject_tests.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) 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()) From 6cc38182dae729b6ea0666ee7634b0139ead4bb2 Mon Sep 17 00:00:00 2001 From: Dan Shapiro <3732858+danshapiro@users.noreply.github.com> Date: Sat, 19 Sep 2026 21:01:20 -0700 Subject: [PATCH 2/2] test(session-handoff): poll for the SETTLED under-ticket binding row, not the first match MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The r27 F2 test's bounded-poll broke on ANY matching binding row, but an interim unfenced row (observed pair None) can land before the runner's under-ticket adoption — the row the assertions read. When the interim row won the race the test asserted None against Some(generation) and panicked (isolated ~13% failure rate; the pre-push/base-gate flake). Wait for the row that actually satisfies the contract — one stamped with the supplied handoff (epoch, generation) pair — so the assertions never read an interim row. The regression the test guards against (the adoption writing None and never the pair) still fails at the deadline, now with the full bindings dump. Post-fix: 0/40 isolated runs. --- .../src/session_handoff/tests.rs | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) 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; }