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

  1. Check whether the main Graphite process (config.main_pid) is alive when the panic occurs and inspect its logs for a startup crash
  2. If the main process was OOM-killed, free memory or raise limits and relaunch
  3. Verify the host binary and main binary come from the same build (same IPC message types) to rule out protocol skew
  4. 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

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


AI-assisted analysis of GraphiteEditor/Graphite@c507b35645 (2026-08-16). Data as JSON: /api/errors/db16cc5188344a4a. Report an issue: GitHub.