{"record":{"id":"f4b1ccc1c091d07b","repo":"facebook/flow","slug":"daemon-spawn-timed-out-waiting-for-child-to-conn","errorCode":null,"errorMessage":"Daemon::spawn: timed out waiting for child to connect","messagePattern":"Daemon::spawn: timed out waiting for child to connect","errorType":"exception","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"rust_port/crates/flow_daemon/src/daemon.rs","lineNumber":411,"sourceCode":"fn accept_with_token(\n    listener: &TcpListener,\n    expected_token: &[u8; 32],\n    timeout: Duration,\n) -> std::io::Result<TcpStream> {\n    // We must defend against a racing local process connecting to our\n    // ephemeral port. Loop accepting until we see a connection bearing the\n    // expected token; reject and close anything else. Bound by `timeout`.\n    //\n    // `TcpListener::accept` is unconditionally blocking. To honor the\n    // deadline we put the listener into non-blocking mode and poll. Polling\n    // sleeps 10ms between attempts -- a bounded busy-wait -- which is\n    // negligible because the child is expected to connect within milliseconds\n    // of exec.\n    listener.set_nonblocking(true)?;\n    let deadline = std::time::Instant::now() + timeout;\n    loop {\n        if std::time::Instant::now() >= deadline {\n            return Err(std::io::Error::new(\n                std::io::ErrorKind::TimedOut,\n                \"Daemon::spawn: timed out waiting for child to connect\",\n            ));\n        }\n        let (mut stream, _peer) = match listener.accept() {\n            Ok(pair) => pair,\n            Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {\n                std::thread::sleep(Duration::from_millis(10));\n                continue;\n            }\n            Err(e) => return Err(e),\n        };\n        // The accepted stream inherits the listener's nonblocking flag on\n        // some platforms; ensure it is blocking and bounded by remaining time.\n        stream.set_nonblocking(false)?;\n        stream.set_nodelay(true)?;\n        let remaining = deadline.saturating_duration_since(std::time::Instant::now());\n        if remaining.is_zero() {","sourceCodeStart":393,"sourceCodeEnd":429,"githubUrl":"https://github.com/facebook/flow/blob/f88ac94bcf6992f5d5a158854d94613ebb92c6e6/rust_port/crates/flow_daemon/src/daemon.rs#L393-L429","documentation":"Daemon::spawn sets up two loopback TCP listeners on ephemeral ports, execs the child with the ports and a 32-byte token, then polls accept() (non-blocking, 10 ms sleep) until a connection arrives, bounded by a timeout (accept_with_token in flow_daemon/src/daemon.rs). \"timed out waiting for child to connect\" means the deadline passed without any accepted connection: the child never reached connect() on the parent's listener.","triggerScenarios":"The spawned child binary fails to exec (missing file, bad arguments, dynamic-loader error) or crashes before connecting; the child is too slow to start (cold page cache, heavy load) and connects after the deadline; a sandbox or firewall blocks loopback TCP connections.","commonSituations":"Missing or mismatched daemon binary on PATH; parent and child from different installs/versions; CI sandboxes that block socket() or loopback binds; wrong-architecture binary (x86 binary on an arm host) failing instantly at exec.","solutions":["Capture and read the child's stderr/stdout — exec failures and early crashes print there; fix whatever it reports (missing binary, bad args).","Verify the child binary exists, is executable, and is the same build/arch/version as the parent.","Increase the spawn timeout so slow or loaded machines still fit inside the deadline.","Check the environment allows loopback TCP (container/sandbox policy, firewall rules)."],"exampleFix":"// before: tight timeout, child stderr discarded\nlet daemon = Daemon::spawn(cmd, args, Duration::from_millis(500))?;\n\n// after: keep child stderr piped for diagnosis, allow slow starts\nlet daemon = Daemon::spawn(cmd, args, Duration::from_secs(10))?; // and pipe stderr to a log","handlingStrategy":"retry","validationCode":"use std::process::Command;\n\n// Cheap pre-flight before Daemon::spawn: fail fast with a clear error\n// instead of waiting out the timeout when the child cannot exec at all.\nfn child_can_exec(bin: &str) -> std::io::Result<()> {\n    Command::new(bin).arg(\"--version\").status()?; // Err if the binary cannot be spawned\n    Ok(())\n}","typeGuard":"fn is_spawn_connect_timeout(e: &std::io::Error) -> bool {\n    e.kind() == std::io::ErrorKind::TimedOut\n        && e.to_string().contains(\"waiting for child to connect\")\n}","tryCatchPattern":"On TimedOut 'waiting for child to connect': log the child's stderr (pipe it, do not discard), verify the binary exists and matches the parent's version, then retry the spawn once with a larger timeout. Escalate to a 'daemon failed to start' error if the retry also times out.","preventionTips":["Pin parent and child to the same binary path/version; no mixed installs on PATH.","Pipe child stderr to a log so exec failures are diagnosable the first time.","Budget spawn timeouts for loaded machines (seconds, not milliseconds).","Ensure the runtime environment permits loopback TCP."],"tags":["daemon","spawn","timeout","tcp","rust"],"backgroundTag":"child-process-spawn-timeout","analyzedSha":"f88ac94bcf6992f5d5a158854d94613ebb92c6e6","analyzedAt":"2026-08-20T10:41:37.992Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}