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
- Treat a flush failure as child-gone: wait on the child (`try_wait`), restart the daemon, and resend the message.
- Keep parent and child on the same build so the child does not exit on unrecognized messages.
- Investigate why the child exited (its stderr/logs) — the flush error is a symptom, the child's death is the cause.
- 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
- Remember flush is where buffered write errors surface — a flush failure usually means the child died earlier, not that flush itself is broken.
- Check child status (try_wait) before and after sends in long sessions.
- Version both sides of the daemon protocol together to prevent early child exits.
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
- Daemon::to_channel: bincode serialize
- Daemon child: failed to write token to parent out-socket: {}
- Daemon child: failed to write token to parent in-socket: {}
- Monitor_died (EPIPE)
- failed to flush json errors
AI-assisted analysis of facebook/flow@f88ac94bcf (2026-08-20).
Data as JSON: /api/errors/d3fdcd122f7fddbc.
Report an issue: GitHub.