GraphiteEditor/Graphite · error

Failed to create control channel

Error message

Failed to create control channel

What it means

Panic when ipc_channel::ipc::channel::<HostControlMessage>() fails. This call creates the OS-backed channel (socketpair on Unix, named pipe on Windows) that the host sends to the main process inside its Hello message for reverse-direction control messages. Channel creation fails when the process is out of file descriptors (EMFILE), when kernel object limits are hit, or when the sandbox blocks the underlying syscall.

Source

Thrown at desktop/ui/src/remote/host.rs:31

use crate::platform::mac;

pub(crate) fn run() {
	// Ignore SIGINT, the controlling process is responsible for shutting down the host
	#[cfg(any(target_os = "linux", target_os = "macos"))]
	unsafe {
		libc::signal(libc::SIGINT, libc::SIG_IGN);
	}

	let args: Vec<String> = std::env::args().collect();
	let config = HostConfig::from_args(&args).expect("CEF host started without a valid host config argument");
	let acceleration_requested = config.acceleration;

	#[cfg(target_os = "macos")]
	mac::spawn_parent_watchdog(config.main_pid);

	let bootstrap = IpcSender::<EventMessage>::connect(config.server.clone()).expect("Failed to connect to the main process bootstrap server");
	let event_sender = Arc::new(Mutex::new(bootstrap));
	let (control_sender, control_receiver) = ipc_channel::ipc::channel::<HostControlMessage>().expect("Failed to create control channel");

	#[cfg(feature = "accelerated_paint")]
	let plane = if acceleration_requested { PlaneSender::from_config(&config, event_sender.clone()) } else { None };
	#[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(),

View on GitHub (pinned to c507b35645)

Solutions

  1. Raise the file-descriptor limit for the app (ulimit -n before launch, LimitNOFILE= in systemd, --ulimit nofile= in docker)
  2. Inspect /proc/<host-pid>/fd to count open descriptors and find leaks if the failure appears over time
  3. Restart the app to release leaked descriptors and confirm the limit was the cause
  4. If sandboxed, allow the socketpair syscall family in the security policy

Example fix

# before (shell)
./graphite  # host panics: Failed to create control channel

# after
ulimit -n 4096
./graphite
Defensive patterns

Strategy: retry

Validate before calling

// Cheap canary: if fd count is near the limit, channel creation is likely to fail
fn fd_headroom(threshold: usize) -> bool {
	let open = std::fs::read_dir("/proc/self/fd").map(|d| d.count()).unwrap_or(0);
	open < threshold
}

Try / catch

let (control_sender, control_receiver) = match ipc_channel::ipc::channel::<HostControlMessage>() {
	Ok(ch) => ch,
	Err(e) if e.to_string().contains("Too many open files") => {
		std::thread::sleep(Duration::from_millis(50));
		ipc_channel::ipc::channel::<HostControlMessage>().expect("control channel creation failed after retry")
	}
	Err(e) => panic!("Failed to create control channel: {e}"),
};

Prevention

When it happens

Trigger: The UI host process exhausting its fd limit because many CEF browsers/pipes/sockets are open; a low ulimit -n in the service/container the host runs under; a seccomp/LD_PRELOAD sandbox interfering with socketpair creation.

Common situations: Long editor sessions accumulating leaked file descriptors; containers or systemd services started with LimitNOFILE low; heavy multi-window usage spawning many IPC channels; CI environments with restrictive rlimits.

Related errors


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