facebook/flow · error

Daemon::to_channel: flush failed

Error message

Daemon::to_channel: flush failed

What it means

After bincode-encoding a message into the daemon channel, `to_channel` flushes the stream with `oc.stream.flush().expect("Daemon::to_channel: flush failed")`. Because the encode step buffers, errors that occurred during writing often only surface at flush time; in practice this panic means the write end hit EPIPE — the daemon child has exited or closed its pipe — or the stream is otherwise unusable.

Source

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

// type 'a out_channel = Stdlib.out_channel
pub struct OutChannel<T> {
    stream: TcpStream,
    _phantom: PhantomData<fn(T)>,
}

pub struct ChannelPair<In, Out>(pub InChannel<In>, pub OutChannel<Out>);

pub struct Handle<In, Out> {
    pub channels: ChannelPair<In, Out>,
    pub child: Child,
}

pub fn to_channel<T: Serialize>(oc: &mut OutChannel<T>, v: &T, should_flush: bool) {
    bincode::serde::encode_into_std_write(v, &mut oc.stream, bincode::config::legacy())
        .expect("Daemon::to_channel: bincode serialize");
    if should_flush {
        oc.stream.flush().expect("Daemon::to_channel: flush failed");
    }
}

pub fn from_channel<T: DeserializeOwned>(ic: &mut InChannel<T>, timeout: Option<Duration>) -> T {
    try_from_channel(ic, timeout).expect("Daemon::from_channel: bincode deserialize")
}

pub fn try_from_channel<T: DeserializeOwned>(
    ic: &mut InChannel<T>,
    timeout: Option<Duration>,
) -> Result<T, bincode::error::DecodeError> {
    ic.stream
        .set_read_timeout(timeout)
        .map_err(|e| bincode::error::DecodeError::Io {
            inner: e,
            additional: 0,
        })?;
    bincode::serde::decode_from_std_read(&mut ic.stream, bincode::config::legacy())

View on GitHub (pinned to f88ac94bcf)

Solutions

  1. Treat a flush failure as child-gone: wait on the child (`try_wait`), restart the daemon, and resend the message.
  2. Keep parent and child on the same build so the child does not exit on unrecognized messages.
  3. Investigate why the child exited (its stderr/logs) — the flush error is a symptom, the child's death is the cause.
  4. Code fix: propagate the io::Error instead of expect and handle BrokenPipe by respawning.

Example fix

// before
if should_flush {
    oc.stream.flush().expect("Daemon::to_channel: flush failed");
}

// after
if should_flush {
    if let Err(e) = oc.stream.flush() {
        // buffered writes surface EPIPE here: child is gone
        return Err(e); // caller respawns the daemon and retries the send
    }
}
Defensive patterns

Strategy: try-catch

Try / catch

if let Err(e) = oc.stream.flush() {
    if e.kind() == std::io::ErrorKind::BrokenPipe {
        // buffered write surfaced EPIPE at flush: child exited; respawn and resend
    }
}

Prevention

When it happens

Trigger: The child process dies (crash, OOM kill, external kill) after the message was buffered but before the flush completes; the child closes stdin mid-session; fd exhaustion leaving the stream broken.

Common situations: Daemon children reaped by resource managers or sandboxes; child panics during an earlier message that only surface on the next flush; mismatched protocol versions causing the child to bail.

Related errors


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