Skip to content

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

@jadrian2006

Description

@jadrian2006

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

Summary

This is the Linux/macOS counterpart to #2228 (Windows desktop OAuth callbacks do not reach the running app instance), which was resolved by PR #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

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

Status

Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions