GraphiteEditor/Graphite · error
Failed to send Hello to the main process
Error message
Failed to send Hello to the main process
What it means
Panic when the UI host's event_sender.send(EventMessage::Hello { ... }) fails. This ipc-channel send fails when the receiving end in the main Graphite process is gone: the main process exited or crashed after spawning the host, or it dropped the bootstrap receiver before the handshake completed. The preceding expect on the mutex ('cannot be poisoned before threads exist') indicates the send itself, not the lock, is the failure point.
Source
Thrown at desktop/ui/src/remote/host.rs:53
#[cfg(feature = "accelerated_paint")]
let acceleration = plane.is_some();
#[cfg(not(feature = "accelerated_paint"))]
let acceleration = {
if acceleration_requested {
tracing::error!("UI acceleration requested but the accelerated_paint feature is disabled; using software frames");
}
false
};
event_sender
.lock()
.expect("The host message sender cannot be poisoned before threads exist")
.send(EventMessage::Hello {
pid: std::process::id(),
control_sender,
acceleration,
})
.expect("Failed to send Hello to the main process");
let sequence = Arc::new(SequenceState::new());
let frames = FrameStreamer::new(
event_sender.clone(),
sequence.clone(),
#[cfg(feature = "accelerated_paint")]
plane,
);
let (view_info_sender, view_info_receiver) = std::sync::mpsc::channel();
let delegate = BrowserDelegate::new(event_sender.clone(), view_info_receiver);
let context = match CefContext::create(delegate, frames, view_info_sender, acceleration) {
Ok(context) => {
if let Ok(sender) = event_sender.lock() {
let _ = sender.send(EventMessage::BrowserCreated);
}
context
}View on GitHub (pinned to c507b35645)
Solutions
- Check whether the main Graphite process (config.main_pid) is alive when the panic occurs and inspect its logs for a startup crash
- If the main process was OOM-killed, free memory or raise limits and relaunch
- Verify the host binary and main binary come from the same build (same IPC message types) to rule out protocol skew
- Re-launch the app; capture both processes' logs if reproducible
Example fix
// before
.expect("Failed to send Hello to the main process");
// after
if let Err(e) = event_sender.lock().expect("The host message sender cannot be poisoned before threads exist").send(EventMessage::Hello {
pid: std::process::id(),
control_sender,
acceleration,
}) {
eprintln!("Failed to send Hello to the main process: {e} (main process exited during handshake)");
std::process::exit(1);
} Defensive patterns
Strategy: try-catch
Try / catch
let hello_result = event_sender
.lock()
.expect("The host message sender cannot be poisoned before threads exist")
.send(EventMessage::Hello { pid: std::process::id(), control_sender, acceleration });
if hello_result.is_err() {
eprintln!("main process exited during Hello handshake; shutting down host");
std::process::exit(1);
} Prevention
- Treat the Hello handshake as fallible: the parent can die at any moment and the host must exit gracefully
- Log the main process pid in host errors to correlate with parent crashes
- Watch for OOM kills of the main process when hosts repeatedly fail the handshake at startup
When it happens
Trigger: Main process dying between the host's connect() and the Hello send; main process crashing during startup so the bootstrap receiver is dropped; the host being started with a stale server address that happened to accept a connection but immediately closed it.
Common situations: Startup races where the parent is killed (OOM killer, watchdog, user Ctrl-C during launch); main-process panics in early initialization; mismatched protocol versions where the main process drops unknown senders.
Related errors
- Failed to connect to the main process bootstrap server
- Failed to spawn socket thread
- Failed to spawn the CEF control thread
- The CEF control thread ended without a result
- Failed to create control channel
AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16).
Data as JSON: /api/errors/db16cc5188344a4a.
Report an issue: GitHub.