fix(deeplink): recreate a destroyed main window — deep links must never land in a windowless app - #493
Merged
Merged
Conversation
…ver land in a windowless app
Live failure 2026-08-19: the relay delivered ("Deep-link received via relay"
logged) but the click did nothing — the main window had been DESTROYED, with
no CloseRequested fired and no app-side destroy caller (suspected
WebView2-level death; the notification window died the same way minutes
earlier, "JS never loaded — recreating"). The emit went into a windowless
app, and nothing in the codebase could ever bring main back: deep links,
tray "Show 4DA", and tray left-click all get_webview_window("main") and
silently no-op until an app restart.
ensure_main_window() is the missing primitive: return main, or rebuild it
from its tauri.conf definition. Deep-link delivery (plugin event listener
and relay poll, unified in deliver_deep_link) fronts an existing window and
emits — or, when main is gone, PARKS the URL via the cold-start pending
mechanism and recreates the window; the fresh frontend collects the parked
URL on mount, so no emit races the webview boot. All three tray show paths
use the same primitive. A Destroyed forensic log names the next window
death instead of letting it vanish silently.
Also from the same live trace: the rejected second instance's argv scan ran
validate_deep_link_url, whose rejection path writes a security event via
get_database() — the REJECTED instance was initializing the shared database
(argv[0], the exe path, always fails validation and logged a bogus security
event on every relay). The scan is now a bare scheme-prefix check on
args().skip(1); the primary validates on receipt, where it always did.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EnyENPW2GcG8GU7x1rJ8wN
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The relay delivered — into a windowless app
Operator's live repro (screenshots, 2026-08-19): Chrome's protocol dialog fired, the secondary's console flashed, then nothing. The primary's log shows
Deep-link received via relayat 15:50:31 — the #492 handoff worked — but the main window had been destroyed, soget_webview_window("main")wasNoneand the emit went nowhere. Window introspection confirmed only the hidden briefing window remained.The killer left no trace: no
CloseRequested(hide-to-tray never logged), no app-sidedestroy()caller exists for main (only briefing/notification recreate themselves), and the notification window died the same way minutes earlier ("JS never loaded — recreating") — suspected WebView2-level death. Whatever kills it, nothing in the app could ever bring main back: deep links, tray Show 4DA, and tray left-click all silently no-op until an app restart.Fix:
ensure_main_window()— return main, or rebuild it from its configdeliver_deep_link): window present → front it and emit; window gone → park the URL via the cold-start pending mechanism and recreate the window — the fresh frontend collects the parked URL on mount viatake_pending_deep_link, so no emit races the webview boot.WindowEvent::Destroyedis now logged with its label, so the next silent window death names its moment.Second bug from the same trace: the rejected instance opened the shared DB
The secondary's argv scan ran
validate_deep_link_url, whose rejection path writes a security event viaget_database()— the operator's console shows the REJECTED second instance loggingInitializing database ... 4da.db(argv[0], the exe path, always fails validation → a bogus "rejected deep-link" security event on every relay). That is the exact WAL hazard the pre-Tauri lock exists to prevent. The scan is now a bare scheme-prefix check onargs().skip(1)with zero DB contact; the primary validates on receipt, where it always did.Verification
-D warningsclean on the plain lib target;single_instance8/8,utils::url20/20; size + doc gates pass.🤖 Generated with Claude Code
https://claude.ai/code/session_01EnyENPW2GcG8GU7x1rJ8wN