{"record":{"id":"2e91e95b81876e0a","repo":"facebook/flow","slug":"monitor-died-epipe","errorCode":null,"errorMessage":"Monitor_died (EPIPE)","messagePattern":"Monitor_died \\(EPIPE\\)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"rust_port/crates/flow_monitor_rpc/src/monitor_rpc.rs","lineNumber":160,"sourceCode":"    writer.write_all(&len.to_be_bytes())?;\n    writer.write_all(&json)?;\n    writer.flush()\n}\n\n// Sends a message to the monitor.\n//\n// This is a no-op if the MonitorRPC is disabled. This allows the server to stream things like\n// status updates without worrying whether or not there is a monitor\n//\n// Unliked read, this is synchronous. We don't currently have a use case for async sends, and it's a\n// little painful to thread lwt through to everywhere we send data\nfn send(msg: monitor_prot::ServerToMonitorMessage) {\n    with_outfd(\n        || {},\n        |outfd| {\n            if let Err(e) = flow_parser::loc::with_full_source_serde(|| send_message(outfd, &msg)) {\n                if e.kind() == io::ErrorKind::BrokenPipe {\n                    panic!(\"Monitor_died (EPIPE)\");\n                } else {\n                    log::error!(\"MonitorRPC.send: write failed: {}\", e);\n                }\n            }\n        },\n    );\n}\n\n// Respond to a request from an ephemeral client\npub fn respond_to_request(request_id: monitor_prot::RequestId, response: response::Response) {\n    send(monitor_prot::ServerToMonitorMessage::Response(\n        request_id, response,\n    ));\n}\n\n// Exception while handling the request\npub fn request_failed(request_id: monitor_prot::RequestId, exn_str: String) {\n    send(monitor_prot::ServerToMonitorMessage::RequestFailed(","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/facebook/flow/blob/f88ac94bcf6992f5d5a158854d94613ebb92c6e6/rust_port/crates/flow_monitor_rpc/src/monitor_rpc.rs#L142-L178","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","Find why the monitor died first: monitor logs, dmesg for OOM, and whether the monitor exited on an unrecognized frame.","If it recurs at startup, make sure monitor and server binaries come from the same Flow installation/version.","Never kill only one of the two processes; use the supported stop command so both shut down cleanly."],"exampleFix":"# before: killing only the monitor guarantees a later EPIPE panic in the server\npkill -f flow-server-monitor\n\n# after: stop the pair through the supported entry point\nflow stop","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"use std::panic::{catch_unwind, AssertUnwindSafe};\n\nlet result = catch_unwind(AssertUnwindSafe(|| run_server_main()));\nif result.is_err() {\n    // 'Monitor_died (EPIPE)': the monitor is gone by design.\n    // Fall back to supervised shutdown instead of letting the abort leak.\n    supervisor::restart_monitor_and_server_pair();\n}","preventionTips":["Always stop monitor and server together via the supported stop command; never pkill only one.","Run the pair under one supervisor (systemd/launchd unit) that restarts them as a unit.","Keep monitor and server binaries from the same Flow installation so neither exits on unrecognized frames.","Check dmesg for OOM kills on the monitor when this recurs."],"tags":["broken-pipe","epipe","monitor","server-lifecycle","ipc"],"backgroundTag":"broken-pipe","analyzedSha":"f88ac94bcf6992f5d5a158854d94613ebb92c6e6","analyzedAt":"2026-08-20T10:41:37.992Z","schemaVersion":2},"datasetVersion":"2026-08-23T11:17:13.642Z"}