Skip to content

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

Merged
senamakel merged 5 commits into
tinyhumansai:mainfrom
M3gA-Mind:fix/linux-macos-deep-link-pre-cef
May 23, 2026
Merged

fix(tauri): forward deep-link URLs on Linux before CEF preflight exits secondary#2458
senamakel merged 5 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

Release Notes

  • Bug Fixes
    • Fixed deep-link URL handling on Linux for OAuth callbacks and other URL schemes.

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

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8a2a72fa-55c9-4221-aeb9-0e99b36b2944

📥 Commits

Reviewing files that changed from the base of the PR and between f4bebd6 and 49cb9e9.

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

📝 Walkthrough

Walkthrough

Introduces a Linux-only Unix socket-based IPC mechanism to forward openhuman:// OAuth callback URLs from secondary instances directly to the primary Tauri/CEF application before CEF cache-lock preflight can exit, preserving deep-link OAuth flows in Linux environments.

Changes

Linux OAuth deep-link forwarding

Layer / File(s) Summary
Module doc, socket path, and URL extraction
app/src-tauri/src/deep_link_ipc.rs
Module-level documentation explains the Linux pre-CEF race condition. socket_path() computes a stable per-user Unix socket location from XDG_RUNTIME_DIR with UID-containing temp fallback. extract_deep_link_urls() filters argv for openhuman:// URLs.
Secondary instance forwarding
app/src-tauri/src/deep_link_ipc.rs
ForwardResult enum and try_forward_deep_links() extract URLs from argv, connect to the primary Unix socket, write newline-delimited URLs with a write timeout, and return Forwarded/NoPrimary/NoUrls based on connection success.
Pending queue and live dispatch
app/src-tauri/src/deep_link_ipc.rs
Global pending queue and live handler storage via OnceLock<Arc<Mutex<_>>>. URL redaction helper for logging. dispatch_url() routes URLs to the live handler if installed, or queues them for later processing.
Primary listener, connection handling, and socket guard
app/src-tauri/src/deep_link_ipc.rs
DeepLinkSocketGuard provides RAII socket-file cleanup via Drop. bind_and_listen() binds the Unix listener, probes/removes stale sockets on AddrInUse, and spawns the accept loop. handle_connection() reads newline-delimited lines with read timeout, filters to openhuman:// URLs, logs/redacts URLs, and dispatches each one.
Live handler installation and draining
app/src-tauri/src/deep_link_ipc.rs
drain_pending_urls() installs a live Tauri emitter for deep-link://new-url events, then parses and emits any URLs collected before setup completed.
Unit tests for socket/path/filter/forwarding
app/src-tauri/src/deep_link_ipc.rs
Tests cover socket_path() behavior under XDG_RUNTIME_DIR set vs unset, argv filtering for openhuman://, bind/connect forwarding roundtrip, and missing-primary failure path.
Integration into app startup
app/src-tauri/src/lib.rs, app/src-tauri/src/deep_link_ipc.rs
Module declaration on Linux. In run(), attempts to forward deep links before CEF, exiting 0 on success; otherwise binds the listener. In setup() closure, drains pending URLs and installs the live Tauri event handler.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • graycyrus

Poem

🐇 A socket springs forth on Linux plains,
Where URLs hop through named-pipe veins,
OAuth callbacks find their way,
No CEF preflight bars the day,
Secondary whispers to the prime,
And deep-link flows arrive on time! 🔗

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly describes the main fix: implementing deep-link URL forwarding on Linux before CEF preflight exits a secondary process.
Linked Issues check ✅ Passed The PR implementation directly addresses all objectives from issue #2359: Unix socket-based pre-CEF deep-link forwarding on Linux with proper primary/secondary handling, socket path stability, and integrated setup.
Out of Scope Changes check ✅ Passed All changes are scoped to the stated objectives: new deep_link_ipc module (Linux-only), lib.rs integration for deep-link forwarding, and a German translation update unrelated to the core fix.

✏️ 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).

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 21, 2026
@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.

# Conflicts:
#	app/src-tauri/src/lib.rs
senamakel and others added 2 commits May 22, 2026 19:16
The `settings.mcpServer.*` and `settings.developerMenu.mcpServer.*` keys
at lines 526-547 in de-5.ts duplicate the earlier block at 211-235,
causing `tsc --noEmit` to fail with TS1117 ("object literal cannot have
multiple properties with the same name") and blocking the Type Check
and E2E Appium jobs on every open PR.

This is the same shape as tinyhumansai#2495 ("remove duplicate German keys
unblocking main's Type Check"); a fresh duplicate slipped in during a
later i18n batch.

Removes only the duplicate block; the earlier (more idiomatic German:
"Verfügbare Werkzeuge" vs "Verfügbare Tools") definitions are retained.
@senamakel senamakel merged commit 1cb6a14 into tinyhumansai:main May 23, 2026
22 of 23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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)

3 participants