facebook/flow · error · std::io::Error

Daemon::spawn: timed out reading token from child

Error message

Daemon::spawn: timed out reading token from child

What it means

After accept_with_token accepts a connection, it must read exactly 32 token bytes within the deadline's remaining time (set via the accepted stream's read timeout) and match them against the expected token, to defend against unrelated local processes grabbing the ephemeral port. "timed out reading token from child" means something connected but sent fewer than 32 bytes before the remaining budget ran out — the handshake started but never completed.

Source

Thrown at rust_port/crates/flow_daemon/src/daemon.rs:430

                std::io::ErrorKind::TimedOut,
                "Daemon::spawn: timed out waiting for child to connect",
            ));
        }
        let (mut stream, _peer) = match listener.accept() {
            Ok(pair) => pair,
            Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => {
                std::thread::sleep(Duration::from_millis(10));
                continue;
            }
            Err(e) => return Err(e),
        };
        // The accepted stream inherits the listener's nonblocking flag on
        // some platforms; ensure it is blocking and bounded by remaining time.
        stream.set_nonblocking(false)?;
        stream.set_nodelay(true)?;
        let remaining = deadline.saturating_duration_since(std::time::Instant::now());
        if remaining.is_zero() {
            return Err(std::io::Error::new(
                std::io::ErrorKind::TimedOut,
                "Daemon::spawn: timed out reading token from child",
            ));
        }
        stream.set_read_timeout(Some(remaining))?;
        let mut received = [0u8; 32];
        match stream.read_exact(&mut received) {
            Ok(()) if &received == expected_token => {
                stream.set_read_timeout(None)?;
                return Ok(stream);
            }
            Ok(()) => {
                tracing::warn!(
                    target: "flow_daemon",
                    "Daemon::spawn: rejected connection with bad token from {:?}",
                    stream.peer_addr().ok()
                );
                drop(stream);

View on GitHub (pinned to f88ac94bcf)

Solutions

  1. Ensure the parent and child are the same build and version of the binary (mixed installs on PATH are the top cause).
  2. Increase the overall spawn timeout so a budget is left for the token read after connect (this error often means connect consumed the whole window).
  3. Check the child's logs for a crash or stall immediately after connecting.
  4. Retry the spawn — silent stray connections on ephemeral ports are rare and transient.

Example fix

// before: connect inside a 1s budget leaves ~0ms for the token read
let daemon = Daemon::spawn(cmd, args, Duration::from_secs(1))?;

// after: give connect + token read a shared, realistic budget
let daemon = Daemon::spawn(cmd, args, Duration::from_secs(10))?;
Defensive patterns

Strategy: retry

Type guard

fn is_token_read_timeout(e: &std::io::Error) -> bool {
    e.kind() == std::io::ErrorKind::TimedOut
        && e.to_string().contains("timed out reading token from child")
}

Try / catch

On TimedOut 'reading token from child': retry the spawn once (racing silent connections on ephemeral ports are transient). If it recurs, check version skew between parent and child binaries and read the child's logs for a stall right after connect.

Prevention

When it happens

Trigger: Version skew where the child speaks a different handshake than the parent expects; the child hangs between connect() and its first write; a racing local process connects to the ephemeral port and stays silent, eating the remaining deadline.

Common situations: Parent and child binaries from different Flow versions or install prefixes on the same machine; the child stalling immediately after connect under heavy load or a debugger; parallel tests on loopback increasing stray-connection odds.

Understand the failure class

Related errors


AI-assisted analysis of facebook/flow@f88ac94bcf (2026-08-20). Data as JSON: /api/errors/8084b65d7aadfaf5. Report an issue: GitHub.