mgth/LittleBigMouse · critical

failed to start per-user local IPC

Error message

failed to start per-user local IPC: {error}

What it means

The lbm-hook Rust daemon's main() starts its per-user local IPC server (ipc::server::start, or start_with_endpoint when LBM_HOOK_ENDPOINT is set) before entering its listen loop. If the server cannot bind/start, the Result is unwrapped with a panic! carrying the underlying IPC error, aborting the hook process at startup.

Solutions

  1. Remove the stale socket/endpoint file (or choose a fresh endpoint) and restart the hook.
  2. If LBM_HOOK_ENDPOINT is set, verify it points to an existing, writable location not already in use; unset it to fall back to the default per-user endpoint.
  3. Ensure the per-user runtime directory (XDG runtime dir / tmp equivalent) exists and is writable by the user running the hook.
  4. Kill any leftover hook/daemon process still bound to the endpoint before relaunching.

Example fix

// before
LBM_HOOK_ENDPOINT=/run/user/1000/lbm-test ./lbm-hook // dir missing -> panic
// after
mkdir -p /run/user/1000/lbm-test && rm -f /run/user/1000/lbm.sock && ./lbm-hook
Defensive patterns

Strategy: validation

Validate before calling

// before launching the hook
if let Ok(ep) = std::env::var("LBM_HOOK_ENDPOINT") {
    let p = std::path::Path::new(&ep);
    if p.exists() { eprintln!("endpoint {} already exists (stale socket?) — remove or change it", ep); }
    else if let Some(dir) = p.parent() {
        if !dir.is_dir() { eprintln!("endpoint parent {} does not exist", dir.display()); }
    }
}

Type guard

fn endpoint_usable(ep: &str) -> bool {
    let p = std::path::Path::new(ep);
    !p.exists() && p.parent().map(|d| d.is_dir()).unwrap_or(false)
}

Try / catch

match std::panic::catch_unwind(|| start_hook(shared)) {
    Ok(()) => {},
    Err(_) => eprintln!("[LittleBigMouse.Hook] IPC startup failed: remove stale socket or free the endpoint, then retry"),
}

Prevention

When it happens

Trigger: Running the hook binary when the IPC socket/endpoint cannot be created or bound — a stale socket file at the default endpoint path, LBM_HOOK_ENDPOINT set to an unusable path or address, a permission problem in the runtime directory, or another (leftover) process already holding the endpoint.

Common situations: A previous crashed hook instance left a stale socket file; side-by-side testing with LBM_HOOK_ENDPOINT pointing at a directory that does not exist or is not writable; running under a sandbox (Flatpak/systemd) without access to the default runtime dir; two hook instances racing on the same endpoint.

Related errors


AI-assisted analysis of mgth/LittleBigMouse@7a42f01d47 (2026-09-16). Data as JSON: /api/errors/363a97d08893a6cf. Report an issue: GitHub.

Appendix: source

Thrown at rust/crates/lbm-hook/src/main.rs:29

use littlebigmouse_hook::{daemon, hook, ipc, platform};

fn main() {
    platform::init();

    let shared: &'static Shared = SHARED.get_or_init(Shared::new);

    // main() is the event-loop thread; record it before the server can accept a
    // command that would signal it.
    hook::register_main_thread(shared);

    // `LBM_HOOK_ENDPOINT` overrides the per-session pipe/socket path, enabling
    // side-by-side testing next to a running daemon (successor of the old
    // LBM_HOOK_PORT override).
    let (server, endpoint) = match std::env::var("LBM_HOOK_ENDPOINT") {
        Ok(endpoint) => ipc::server::start_with_endpoint(shared, endpoint),
        Err(_) => ipc::server::start(shared),
    }
    .unwrap_or_else(|error| panic!("failed to start per-user local IPC: {error}"));
    let _ = shared.server.set(server);

    eprintln!("[LittleBigMouse.Hook] listening on {endpoint}");

    // C++ Program.cpp: UI mode (wait for socket commands) when launched by the UI
    // (parent path contains "LittleBigMouse"); otherwise standalone — load the
    // last saved layout and start hooking. `LBM_HOOK_UI` forces UI mode for tests.
    let ui_mode = std::env::var_os("LBM_HOOK_UI").is_some()
        || platform::process::parent_process_path()
            .map(|p| p.contains("LittleBigMouse"))
            .unwrap_or(false);

    if !ui_mode {
        if let Some(path) = platform::paths::lbm_data_file("Current.xml") {
            eprintln!(
                "[LittleBigMouse.Hook] standalone mode: loading {}",
                path.display()
            );

View on GitHub (pinned to 7a42f01d47)