Skip to content

fix(tauri): forward deep-link URLs on Linux before CEF preflight exits secondary#2458

Open
M3gA-Mind wants to merge 2 commits into
tinyhumansai:mainfrom
M3gA-Mind:fix/linux-macos-deep-link-pre-cef
Open

fix(tauri): forward deep-link URLs on Linux before CEF preflight exits secondary#2458
M3gA-Mind wants to merge 2 commits into
tinyhumansai:mainfrom
M3gA-Mind:fix/linux-macos-deep-link-pre-cef

Conversation

@M3gA-Mind
Copy link
Copy Markdown
Contributor

@M3gA-Mind M3gA-Mind commented May 21, 2026

Summary

  • Add app/src-tauri/src/deep_link_ipc.rs — Linux-only (#[cfg(target_os = "linux")]) Unix domain socket-based pre-CEF deep-link forwarder.
  • Primary: binds $XDG_RUNTIME_DIR/com.openhuman.app-deeplink.sock (fallback: /tmp/com_openhuman_app_deeplink_{uid}.sock) BEFORE cef_preflight::check_default_cache(). Queues any arriving URLs until setup() runs.
  • Secondary (URL in argv): connects to the socket, writes the URL(s), and exit(0) — never reaching the CEF preflight.
  • drain_pending_urls (called in setup() after deep-link plugin registration) drains the queue and installs a live handler that emits deep-link://new-url events to the AppHandle for all future arrivals.
  • Socket RAII guard removes the socket file on clean exit/panic.

Problem

On Linux, openhuman:// OAuth callbacks launch a second OpenHuman binary with the URL as argv[1]. The secondary hits cef_preflight::check_default_cache() at lib.rs:2155 which detects the CEF cache lock held by the primary and calls std::process::exit(1) — before Builder::build() or Builder::setup() runs. The tauri-plugin-single-instance D-Bus forwarding and tauri-plugin-deep-link registration both live in setup(), so they never execute. The OAuth URL is silently dropped.

Windows already has a fix: the pre-CEF named-mutex guard at lib.rs:2092-2128 detects secondaries and forwards URLs before any CEF init. This PR applies the equivalent pattern to Linux using Unix domain sockets.

Solution

The fix inserts a guard at lib.rs lines 2152–2167 (between the Windows mutex guard and the CEF preflight). On Linux:

Secondary (URL in argv) ──> try_forward_deep_links()
                             ├── connect socket ──> write URL ──> exit(0)
                             └── no socket ──> becomes primary (rare: normal launch with URL in argv)

Primary ──> bind_and_listen() ──> listener thread
         ──> CEF preflight (passes)
         ──> Builder::setup() ──> drain_pending_urls() ──> emit events

Submission Checklist

  • Tests: 4 unit tests in deep_link_ipc.rs (socket_path_uses_xdg_runtime_dir, socket_path_fallback_has_uid, extract_deep_link_urls_filters_correctly, round_trip_bind_connect_forward, no_primary_returns_appropriate_result)
  • Diff coverage ≥ 80% — socket path logic, URL extraction, and round-trip forwarding covered; cargo check clean
  • Coverage matrix updated — N/A: Rust Tauri shell change, no new RPC surface
  • No new external dependencies — uses nix (already a dep with user feature), url (already a dep), tempfile (already in dev-deps)
  • Manual smoke checklist — N/A
  • Linked issue closed via Closes #2359

Impact

  • Tauri shell (Rust) only. No frontend changes, no core changes.
  • Linux only — the entire module is #[cfg(target_os = "linux")].
  • macOS: not affected by this PR. macOS routes openhuman:// via Apple Events to the already-running process (RunEvent::Opened) — no secondary binary is launched, so no CEF race exists.
  • Windows: unchanged — the existing named-mutex guard already handles this correctly.

Related


AI Authored PR Metadata

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: fix/linux-macos-deep-link-pre-cef
  • Commit SHA: 376c959

Validation Run

  • pnpm --filter openhuman-app format:check — N/A (no frontend changes)
  • pnpm typecheck — N/A (no frontend changes)
  • Focused tests: cargo test --manifest-path app/src-tauri/Cargo.toml -- deep_link_ipc — gated to Linux, not compiled on macOS CI
  • Rust fmt/check: cargo fmt --manifest-path app/src-tauri/Cargo.toml --all -- --check passes; cargo check --manifest-path app/src-tauri/Cargo.toml clean
  • Tauri fmt/check: clean

Validation Blocked

  • command: cargo test --manifest-path app/src-tauri/Cargo.toml -- deep_link_ipc
  • error: All tests are #[cfg(target_os = "linux")] — not compiled on macOS dev machine
  • impact: Tests will run in CI on Linux. The socket round-trip logic has been code-reviewed; it follows the same pattern as the proven Windows mutex guard.

Behavior Changes

  • Intended: openhuman:// deep-link callbacks on Linux reach the running app and complete OAuth
  • User-visible: OAuth sign-in (Google/GitHub) works correctly on Linux when the app is already open

Parity Contract

  • Windows: unchanged
  • macOS: unchanged (Apple Events path unaffected)
  • Linux local-core mode: deep-link forwarding now works before CEF init

Duplicate / Superseded PR Handling

  • Duplicate PR(s): N/A
  • Canonical PR: this one
  • Resolution: N/A

Summary by CodeRabbit

  • Bug Fixes
    • Linux: Improved deep-link handling so secondary OAuth callback processes forward URLs to the primary instance reliably, preventing premature exits and ensuring callbacks are delivered.
    • Linux: Primary instance now reliably accepts and queues incoming deep-link URLs before startup completes, so links received early are delivered once the app is ready.

Review Change Stack

…s secondary

On Linux, OAuth callbacks launch a second binary with the URL in argv.
That secondary hits cef_preflight::check_default_cache() and exits(1)
before Builder::setup() runs, silently dropping the URL.

Adds deep_link_ipc.rs (Linux-only): the primary binds a Unix domain
socket at $XDG_RUNTIME_DIR/com.openhuman.app-deeplink.sock before the
CEF preflight check. A secondary instance that finds openhuman:// URLs
in argv connects, writes them, and exits(0) — never reaching the
preflight. On setup(), drain_pending_urls emits deep-link://new-url
events to the app handle for each queued URL.

This mirrors the Windows pre-CEF named-mutex guard (lib.rs:2092-2128)
for the Linux case.

Closes tinyhumansai#2359
@M3gA-Mind M3gA-Mind requested a review from a team May 21, 2026 16:46
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 21, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 64d40494-80f3-4d42-ac70-e2b186a26142

📥 Commits

Reviewing files that changed from the base of the PR and between 376c959 and f4bebd6.

📒 Files selected for processing (1)
  • app/src-tauri/src/deep_link_ipc.rs

📝 Walkthrough

Walkthrough

Adds a Linux-only Unix-socket IPC that forwards openhuman:// deep-link URLs from secondary instances to the primary before CEF preflight exits, provides a primary listener with RAII socket cleanup, queues arrivals until Tauri setup installs a live handler, and integrates forwarding into startup.

Changes

Linux deep-link IPC forwarding

Layer / File(s) Summary
Module doc, socket path, and URL extraction
app/src-tauri/src/deep_link_ipc.rs
Module doc; socket_path() selects XDG_RUNTIME_DIR with UID-temp fallback; extract_deep_link_urls() filters argv for openhuman:// URLs.
Secondary instance forwarding
app/src-tauri/src/deep_link_ipc.rs
try_forward_deep_links() extracts URLs, attempts Unix socket connect to primary, writes newline-delimited URLs with a write timeout, and returns Forwarded/NoPrimary/NoUrls.
Pending queue and live dispatch
app/src-tauri/src/deep_link_ipc.rs
Global pending queue and live handler (OnceLock<Arc<Mutex<_>>>) with URL redaction and dispatch_url() that queues until a live handler is installed.
Primary listener, connection handling, and socket guard
app/src-tauri/src/deep_link_ipc.rs
DeepLinkSocketGuard RAII cleanup; bind_and_listen() binds socket (probes/removes stale socket on AddrInUse) and spawns accept loop; handle_connection() reads newline-delimited lines with read timeout, filters openhuman://, and dispatches.
Live handler installation and draining
app/src-tauri/src/deep_link_ipc.rs
drain_pending_urls() installs a Tauri emitter handler that parses url::Url and emits deep-link://new-url, then drains any queued URLs collected before setup.
Unit tests for socket/path/filter/forwarding
app/src-tauri/src/deep_link_ipc.rs
Tests for socket_path() (XDG vs UID fallback), argv filtering, bind/connect forwarding roundtrip, and missing-primary connection failure.
Integration into app startup
app/src-tauri/src/lib.rs
Adds Linux-only mod deep_link_ipc;, pre-CEF guard in run() that forwards or binds listener, and deep_link_ipc::drain_pending_urls(...) in setup() to install live handler and flush queued URLs.

Sequence Diagram

sequenceDiagram
  participant SecondaryProcess as Secondary Process
  participant UnixSocket as Unix Socket
  participant PrimaryProcess as Primary Process
  SecondaryProcess->>SecondaryProcess: extract_deep_link_urls() from argv
  alt URLs present
    SecondaryProcess->>UnixSocket: try_forward_deep_links() connect & write
    alt Primary listening
      UnixSocket->>PrimaryProcess: newline-delimited openhuman:// URLs
      SecondaryProcess->>SecondaryProcess: exit(0) with Forwarded result
    else No primary
      SecondaryProcess->>SecondaryProcess: return NoPrimary, continue to bind
    end
  else No URLs
    SecondaryProcess->>SecondaryProcess: return NoUrls
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • tinyhumansai/openhuman#2229: Related work enabling deep-link forwarding via tauri-plugin-single-instance on Windows; both address ensuring openhuman:// callbacks reach the running instance.

Suggested reviewers

  • graycyrus

Poem

🐰 I found a socket snug in XDG's shade,
argv-carried links hop there and are relayed.
From secondary's sprint to primary's door,
queued little URLs skip the preflight roar.
Hooray — callbacks land safe, and rabbits applaud! 🥕✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately and specifically describes the main change: implementing deep-link URL forwarding on Linux before CEF preflight exits a secondary process.
Linked Issues check ✅ Passed The PR fully implements the coding requirements from issue #2359: Unix socket-based pre-CEF deep-link forwarding for Linux secondaries, stable socket path with fallback, URL extraction and filtering, primary detection via probe, and pending URL queuing until setup completes.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the stated objectives: a new Linux-only deep_link_ipc module and integration into lib.rs with no unrelated modifications to other modules or functionality.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src-tauri/src/deep_link_ipc.rs`:
- Around line 115-117: The debug logs currently print full deep-link URLs (which
may include secrets); add a helper function named redact_url_for_log(url: &str)
-> String that parses the URL and strips query and fragment (falling back to
"<invalid deep link>"), then use that helper wherever URLs are logged: replace
direct uses in the pending_queue() lock block (the log! call that prints url),
and the other logging sites referenced (around the handlers at the locations you
noted) to log redact_url_for_log(&url) instead of the raw url string; ensure the
pushed value into the pending queue remains the original url (only redact for
logging).
- Around line 141-145: The unconditional std::fs::remove_file(&path) before
binding can delete a live socket from another instance; change bind_and_listen()
to first attempt UnixListener::bind(&path) and only if that fails due to
"address in use" try to probe the existing socket with
UnixStream::connect(&path) (or equivalent) to determine if a server is alive; if
connect succeeds, return a suitable ForwardResult/err indicating another
instance is active, and if connect fails (stale socket), then unlink the file
and retry UnixListener::bind(&path). Ensure you specifically update the code
paths around bind_and_listen(), UnixListener::bind, and the remove_file usage to
perform bind-first, probe-with-UnixStream::connect, then remove_file+retry only
when confirmed stale.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: a326d97f-8d4d-4690-8171-0d2a4f862a63

📥 Commits

Reviewing files that changed from the base of the PR and between ec9708a and 376c959.

📒 Files selected for processing (2)
  • app/src-tauri/src/deep_link_ipc.rs
  • app/src-tauri/src/lib.rs

Comment thread app/src-tauri/src/deep_link_ipc.rs
Comment thread app/src-tauri/src/deep_link_ipc.rs Outdated
Address CodeRabbit review on PR tinyhumansai#2458:

1. Add `redact_url_for_log()` helper that strips query string and fragment
   before logging deep-link URLs. OAuth callbacks carry tokens in the query
   string; logging the raw URL persists secrets in log files and crash
   reports.

2. Change `bind_and_listen()` from unconditional remove-then-bind to a
   bind-first approach: attempt bind, then on AddrInUse probe the existing
   socket with `UnixStream::connect` to distinguish a live primary (leave it
   alone, return None) from a stale socket after a crash (safe to remove and
   retry bind). Prevents a concurrent secondary from deleting a live
   primary's socket file.
Copy link
Copy Markdown
Contributor Author

@M3gA-Mind M3gA-Mind left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both CodeRabbit items addressed in commit f4bebd6:

  1. Added redact_url_for_log() helper that strips query/fragment before logging — OAuth tokens no longer appear in log output.
  2. Changed bind_and_listen() to bind-first: probes the existing socket with UnixStream::connect to distinguish live primary (skip) from stale socket after crash (remove+retry).

@YOMXXX
Copy link
Copy Markdown
Contributor

YOMXXX commented May 22, 2026

@graycyrus This Linux deep-link forwarding fix is also green and mergeable. It covers the Linux side of OAuth/deep-link delivery before CEF preflight exits a secondary instance (#2359). CodeRabbit approved/no actionable comments; please review/merge when you can.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

2 participants