Skip to content

Linux/macOS: OAuth callbacks via openhuman:// don't reach running app instance (Linux equivalent of #2228 Windows fix) #74

@ElioNeto

Description

@ElioNeto

Original issue tinyhumansai#2359 by @jadrian2006 on 2026-05-20T16:21:59Z


Linux/macOS: OAuth callbacks via openhuman:// don't reach running app instance (Linux equivalent of tinyhumansai#2228 Windows fix)

Summary

This is the Linux/macOS counterpart to tinyhumansai#2228 (Windows desktop OAuth callbacks do not reach the running app instance), which was resolved by PR tinyhumansai#2229 by enabling the deep-link feature on tauri-plugin-single-instance.

That fix alone doesn't help Linux/macOS because the CEF cache-lock preflight at app/src-tauri/src/lib.rs:2074-2078 exits the secondary process with std::process::exit(1) before Tauri's builder runs. The deep-link plugin's forwarding logic lives inside Builder::setup, which is never reached. Result: external openhuman:// callbacks fired by the OS while OpenHuman is already running are dropped, breaking every OAuth flow that returns via deep-link.

Windows works because there is a pre-CEF named-mutex guard at lib.rs:2011-2047 (OPENHUMAN-TAURI-A fix). A second invocation hits ERROR_ALREADY_EXISTS, the secondary's deep-link argv path runs through the plugin's per-platform pre-Builder code, and the URL reaches the primary. Linux/macOS need the equivalent pattern.

Reproduction

  1. Build from source on Pop!_OS 24.04 + COSMIC Wayland + NVIDIA proprietary driver 580 (any Linux desktop session works; this is not Wayland-specific).
  2. Tag v0.54.3-staging. No local patches.
  3. Launch the shell normally (cargo tauri dev -- -- --no-sandbox). Shell + embedded core come up clean.
  4. Start an OAuth flow from inside the shell (e.g. sign-in or "Connect Gmail").
  5. Complete the authentication in the browser.
  6. Browser is redirected to openhuman://auth?token=...&key=auth.
  7. The OS launches a new OpenHuman binary with that URL as argv[1].
  8. The new binary logs [cef-preflight] CEF cache held by host=<host> pid=<primary pid> and exits 1.
  9. The primary shell never receives the callback; OAuth state machine remains in its pending state forever.

Diagnostic log (secondary instance, abbreviated)

[startup] platform: arch=x86_64 os=linux
[cef-profile] configured CEF cache user=local path=/home/USER/.openhuman/users/local/cef
[cef-preflight] CEF cache held by host=pop-os pid=1565481 at /home/USER/.openhuman/users/local/cef

[openhuman] CEF cache at /home/USER/.openhuman/users/local/cef is held by another OpenHuman instance (host pop-os, pid 1565481).
Quit the running instance and try again.

Source of the early exit: app/src-tauri/src/lib.rs:2074-2078

#[cfg(any(target_os = "macos", target_os = "linux"))]
if let Err(e) = cef_preflight::check_default_cache() {
    eprintln!("\n[openhuman] {e}\n");
    std::process::exit(1);
}

Suggested fix

Mirror the Windows pre-CEF guard pattern (lib.rs:2011-2047). On Linux, use a Unix domain socket (or flock(2) over an abstract socket / lockfile) at a stable path like $XDG_RUNTIME_DIR/com.openhuman.app-deeplink.sock. On macOS, use a Mach port or a similar Unix socket. Pseudocode for Linux:

#[cfg(target_os = "linux")]
{
    use std::os::unix::net::UnixStream;
    let sock_path = std::env::var("XDG_RUNTIME_DIR")
        .map(PathBuf::from)
        .unwrap_or_else(|_| std::env::temp_dir())
        .join(format!("{APP_IDENTIFIER}-deeplink.sock"));

    // Collect any openhuman:// URLs from argv
    let urls: Vec<String> = std::env::args()
        .filter(|a| a.starts_with("openhuman://"))
        .collect();

    if !urls.is_empty() {
        if let Ok(mut stream) = UnixStream::connect(&sock_path) {
            // Primary is listening — forward URLs and exit clean.
            for url in &urls {
                let _ = writeln!(stream, "{url}");
            }
            log::info!(
                "[single-instance] forwarded {} deep-link URL(s) to primary at {}; secondary exiting",
                urls.len(),
                sock_path.display()
            );
            std::process::exit(0);
        }
        // No primary listening — fall through; we'll become the primary.
    }

    // Primary path: bind the socket and spawn a listener task that calls
    // the deep-link plugin's on_event hook for each received URL.
    // Listener stays alive for the whole process lifetime.
}

The Tauri plugin's existing on_event handler receives forwarded URLs uniformly via this socket path; the plugin's own D-Bus / argv code stays untouched.

Why the plugin's D-Bus path doesn't help

tauri-plugin-deep-link on Linux registers a D-Bus name in its plugin setup hook, which runs inside Builder::build() — after CEF init. The Builder is never constructed on a secondary instance because cef_preflight::check_default_cache() returns Err and std::process::exit(1) fires first. The plugin's D-Bus forwarding code therefore can't claim the role of "secondary forwarder" in this scenario.

Environment

  • OpenHuman: built from source, tag v0.54.3-staging (commit ebd6457)
  • OS: Pop!_OS 24.04 LTS (Ubuntu noble base)
  • Kernel: 6.18.7-76061807-generic
  • Session: Wayland (COSMIC compositor)
  • GPU: NVIDIA RTX 4070 Ti, driver 580.159.03
  • GLIBC: 2.39
  • Reproducible on every OAuth attempt; the only "OAuth that worked" cases were ones where the user had no OpenHuman running, so the deep-link callback became the new shell with the URL in argv and the OAuth state happened to be re-derivable.

Related

Happy to test patches.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions