facebook/flow · critical

Monitor_died (EPIPE)

Error message

Monitor_died (EPIPE)

What it means

The Flow server pushes ServerToMonitorMessage frames (status updates, responses) to its monitor process over a pipe, and send() writes synchronously. If the write fails with ErrorKind::BrokenPipe, the monitor's read end is gone, and the code panics with 'Monitor_died (EPIPE)' — deliberately killing the server, because a server that lost its monitor cannot be supervised. Other write errors are only logged, so this panic specifically means the peer closed the pipe.

Source

Thrown at rust_port/crates/flow_monitor_rpc/src/monitor_rpc.rs:160

    writer.write_all(&len.to_be_bytes())?;
    writer.write_all(&json)?;
    writer.flush()
}

// Sends a message to the monitor.
//
// This is a no-op if the MonitorRPC is disabled. This allows the server to stream things like
// status updates without worrying whether or not there is a monitor
//
// Unliked read, this is synchronous. We don't currently have a use case for async sends, and it's a
// little painful to thread lwt through to everywhere we send data
fn send(msg: monitor_prot::ServerToMonitorMessage) {
    with_outfd(
        || {},
        |outfd| {
            if let Err(e) = flow_parser::loc::with_full_source_serde(|| send_message(outfd, &msg)) {
                if e.kind() == io::ErrorKind::BrokenPipe {
                    panic!("Monitor_died (EPIPE)");
                } else {
                    log::error!("MonitorRPC.send: write failed: {}", e);
                }
            }
        },
    );
}

// Respond to a request from an ephemeral client
pub fn respond_to_request(request_id: monitor_prot::RequestId, response: response::Response) {
    send(monitor_prot::ServerToMonitorMessage::Response(
        request_id, response,
    ));
}

// Exception while handling the request
pub fn request_failed(request_id: monitor_prot::RequestId, exn_str: String) {
    send(monitor_prot::ServerToMonitorMessage::RequestFailed(

View on GitHub (pinned to f88ac94bcf)

Solutions

  1. Treat it as a shutdown signal, not a bug to patch around: restart the pair (flow stop then start, or reconnect the IDE) so a fresh server and monitor come up together.
  2. Find why the monitor died first: monitor logs, dmesg for OOM, and whether the monitor exited on an unrecognized frame.
  3. If it recurs at startup, make sure monitor and server binaries come from the same Flow installation/version.
  4. Never kill only one of the two processes; use the supported stop command so both shut down cleanly.

Example fix

# before: killing only the monitor guarantees a later EPIPE panic in the server
pkill -f flow-server-monitor

# after: stop the pair through the supported entry point
flow stop
Defensive patterns

Strategy: fallback

Try / catch

use std::panic::{catch_unwind, AssertUnwindSafe};

let result = catch_unwind(AssertUnwindSafe(|| run_server_main()));
if result.is_err() {
    // 'Monitor_died (EPIPE)': the monitor is gone by design.
    // Fall back to supervised shutdown instead of letting the abort leak.
    supervisor::restart_monitor_and_server_pair();
}

Prevention

When it happens

Trigger: The monitor process (flow-server-monitor, e.g. under an IDE integration) crashes, is killed, or exits normally while the server is still streaming: the very next send() from the server hits EPIPE and panics. Happens on status broadcasts as easily as on request responses.

Common situations: IDE extension updated or restarted while an old flow server daemon from the previous session kept running; the monitor OOM-killed (check dmesg); users pkill-ing only the monitor; monitor/server version skew where the monitor exits early on an unrecognized message.

Related errors


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