apps_windows: additional Windows app-discovery sources + heuristic noise filter#8710
Merged
apps_windows: additional Windows app-discovery sources + heuristic noise filter#8710
Conversation
Server tags are determined by URL content, not caller-supplied names. addServerBasedOnURLs now returns the tags of added servers so callers can connect using the actual tag. Also sends VPN status updates from connectToServer on Linux so the UI reflects connection state changes.
* main: don't block first paint on Updater.init() Moving Updater.init() off the critical path to runApp. Investigating a one-shot black-screen-on-startup report on a local macOS dev build (9.0.29 build 487): flutter.log stopped at the last pre-runApp log line with no Dart exception and no crash, while the Go side kept running normally. The only awaited call between that last log and runApp is Updater.init(). Inside init(), the actual update check is already deferred 45 s via Future.delayed + unawaited. But setFeedURL and setScheduledCheckInterval are awaited — both bridge into Sparkle via the auto_updater Flutter plugin, and both can stall on first launch: feed URL resolution, keychain access, or a previous launch's background worker still holding a lock. Any of those becomes a main-isolate hang that prevents runApp, which exactly matches the observed symptom. Fix: drop the await so Updater.init() runs concurrently with the rest of startup. All errors are already handled inside init() itself, so unawaited is safe. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * review: guard sl<Updater>() lookup against failed service injection Copilot flagged that if injectServices() throws above (caught at main.dart:45), Updater is never registered (it's registered at injection_container.dart:40, after storage init), and sl<Updater>() throws synchronously. unawaited() doesn't help — the throw happens before the Future is constructed, so it propagates out of main and prevents runApp. Wrap the call in try/catch + sl.isRegistered<Updater>() so any failure to look up or start Updater.init logs and continues to runApp. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Adam Fisk <afisk@mini.local> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires the FFI path to radiance's ipc.Client.TailLogs and merges in-app flutter.log records so the diagnostic logs view shows both sources. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Picks up: - refactor(vpn): own VPN status on the client so restarts span tunnels - vpn: instrument tunnel.start phases + VPNClient.Restart (#443) The VPN-status-ownership refactor moves setStatus calls out of tunnel and onto VPNClient so a restart transitions Restarting → Disconnecting → Disconnected → Connecting → Connected cleanly. The instrumentation PR adds child spans around libbox.Setup, libbox.NewServiceWithContext, libbox.BoxService.Start, and newMutableGroupManager so SigNoz can attribute the 10s+ tail on /service/start observed in Freshdesk #173696. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nnel (#8702) * lantern-core: dispatch ConnectVPN to SelectServer on live tunnel When the Flutter UI triggers an auto-select on a live tunnel — most visibly Jigar's rewrite of onSmartLocation (server_selection.dart), which routes "switch back to Smart" through startVPN(force: true) → Dart lantern.startVPN() → ffi.go:startVPN → c.ConnectVPN("") — radiance's /vpn/connect endpoint rejects the request with ErrTunnelAlreadyConnected (radiance/vpn/vpn.go:126 in VPNClient.Connect). The error is returned to the Dart UI as a snackbar, the tunnel stays pinned to the previously selected manual server, and lantern.log is silent because neither LocalBackend.ConnectVPN nor VPNClient.Connect slog the ErrTunnelAlready Connected path. Observed on 9.0.30 beta (internal tester, Freshdesk #173763, build from commit 4054689 which includes Jigar's 2895072). After manually picking Bogotá, clicking "Smart" at the top of the server-selection screen surfaces the snackbar and the tunnel keeps routing traffic through the Bogotá samizdat outbound. Fix: when Status() == Connected, LanternCore.ConnectVPN dispatches the request to /server/selected (the live-tunnel outbound swap) instead of /vpn/connect. Empty tag normalizes to vpn.AutoSelectTag — Dart sends "" for Smart, radiance recognizes only the literal "auto" and otherwise falls into the manual-outbound branch of SelectServer, stranding Clash in manual mode with an empty selector. The mapping is centralized in a small normalizeAutoTag helper used by both ConnectVPN and SelectServer. This puts the same dispatch logic that lives in ffi.go:connectToServer onto every caller of LanternCore.ConnectVPN — including ffi.go:startVPN (which Jigar's rewrite now funnels through) and any future FFI/mobile entry point. getlantern/engineering#3291 issue 3. Supersedes earlier work on fisk/connect-dispatch-select-when-connected (485bf5a), which was scoped to this same dispatch but predated the current refactor branch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * vpn_tunnel: dispatch StartVPN to SelectServer on live tunnel (mobile path) Mobile.StartVPN (the gomobile entry point for Android MainActivity and iOS VPNManager) routes through vpn_tunnel.StartVPN(client), which calls client.ConnectVPN(ctx, vpn.AutoSelectTag) directly — bypassing lanterncore.Core. Jigar's onSmartLocation rewrite dispatches "switch back to Smart" through startVPN(force: true), which on Android/iOS lands here. Same ErrTunnelAlreadyConnected bug as the FFI path fixed in the previous commit. Mirror the VPNStatus dispatch pattern garmr already added to vpn_tunnel.ConnectToServer in 4054689: when Status() == Connected, swap outbound via /server/selected; otherwise fall through to the existing /vpn/connect start. Together with the LanternCore.ConnectVPN dispatch, this closes the Smart-from-connected bug on every platform (Windows FFI, Android/iOS gomobile). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ffi: drop now-redundant VPNStatus dispatch in connectToServer LanternCore.ConnectVPN already routes to /server/selected when the tunnel is live (added earlier in this PR), so ffi.go:connectToServer's own VPNStatus check is duplicate work. Collapse to a single c.ConnectVPN call — both the live-tunnel-swap and fresh-connect paths flow through the dispatch one layer down. Behavior unchanged. The "start service failed" error wrapper is kept for Dart-side snackbar stability. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * lantern-core: collapse dispatch to a single implementation in vpn_tunnel Three functions had independent VPNStatus → SelectServer-vs-ConnectVPN dispatches after the earlier commits: LanternCore.ConnectVPN, vpn_tunnel.StartVPN (both added in this PR), and vpn_tunnel.ConnectToServer (pre-existing from 4054689). Consolidate so vpn_tunnel.ConnectToServer is the authoritative dispatch and the other two delegate. - LanternCore.ConnectVPN → vpn_tunnel.ConnectToServer(lc.client, tag) - vpn_tunnel.StartVPN → ConnectToServer(client, vpn.AutoSelectTag) LanternCore.SelectServer keeps its own empty-tag normalization since its scope is the one-shot SelectServer IPC, not the dispatch. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Adam Fisk <afisk@mini.local> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…089) (#8703) Patrick's radiance fac9089 ("fix(vpn): treat the empty string as AutoSelect in SelectServer") is now pinned on this branch via 72a6c62. Radiance normalizes tag == "" → AutoSelectTag on both ConnectVPN and SelectServer, so the client-side normalizations we added earlier (normalizeAutoTag helper in core.go, `if tag == ""` in vpn_tunnel.ConnectToServer) are redundant — radiance handles the Dart "" convention uniformly. Remove: - LanternCore.normalizeAutoTag helper + its use in SelectServer - `if tag == "" { tag = vpn.AutoSelectTag }` branch in vpn_tunnel.ConnectToServer - lantern-core/core_test.go (only tested the removed helper) Behavior unchanged end-to-end: empty tag still means auto-select on every path (FFI, gomobile, connectToServer, startVPN). Co-authored-by: Adam Fisk <afisk@mini.local> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…Server empty-tag fix (#8705) radiance@d5a1872 completes fac9089's empty-string → AutoSelectTag normalization by extending it to LocalBackend.SelectServer, which previously only matched the literal "auto" and fell through to the srvManager lookup for tag == "" — producing "no server found with tag" (HTTP 500, snackbar) on Smart-from-connected flows after the client- side normalization was removed in this branch's 6de3c9a. Reported on Lantern 9.0.30 beta via Freshdesk #173773. go.mod + go.sum bump only; no lantern code changes. Pinned commit: getlantern/radiance@d5a18726afbc (#444). Co-authored-by: Adam Fisk <afisk@mini.local> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(logs): stream diagnostic logs via ipc TailLogs on mobile Adds a mobile gomobile binding for ipc.Client.TailLogs (TailLogs + LogSubscription) and switches Android and iOS to consume it, replacing the per-platform log-file tailers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(logs): stream diagnostic logs via ipc TailLogs on macos Switches the macOS log stream to MobileTailLogs, matching iOS. Removes the file-watching LogTailer (no remaining callers). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(logs): harden TailLogs against nil, panics, and listener leaks - Reject nil listener in mobile.TailLogs; recover from panics crossing the gomobile bridge so the stream survives unexpected bridge errors. - Retain the Kotlin LogListener in a field so the Go side's reference stays strongly rooted on the JVM. - On iOS/macOS, cancel any pre-existing subscription before starting a new one and clear the stored listener when MobileTailLogs errors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(logs): share TailLogs plumbing across mobile and ffi Adds lantern-core/logs.Subscribe wrapping ipc.Client.TailLogs so the mobile and desktop integrations go through one helper. Drops the iOS LogTailer dead code and the unused lantern-core/logging package. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Update log formatting * Fix issue with ios * Fix macos logs issue --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: Jigar-f <jigar@getlantern.org>
…r-operation timeouts (#8707) * ffi: skip the daemon-reachability preflight on Windows / macOS / mobile The 300 ms preflight in lantern-core/core.go's CheckDaemonReachable was originally tuned for the Linux flow (PR #8494 by atavism, commit bf054f4), where the failure path falls back to `systemctl is-active lanternd.service` for a rich diagnostic error. The 300 ms cap made sense as "fast probe → systemd-rich-error", with the systemd query adding the actual user-facing context. Subsequent refactors (commit bd89bea Apr 7, then PR #8578 commit 4d4e06d Apr 16) generalized that preflight to all platforms but the systemd fallback only survived in ffi_linux.go. On Windows / macOS / mobile, ffi_nonlinux.go ended up running the same 300 ms probe with no fallback — just an artificial guillotine in front of ConnectVPN, which has its own "lanternd not reachable" error path with equivalent precision. Cold-start IPC on Windows regularly exceeds 300 ms (named-pipe dial + winio impersonation token dance + H2c connection preface + goroutine scheduling on a 96-second-idle daemon), so the first VPN toggle after launch reliably trips the timeout and shows the user a "lanternd not reachable" error. Clicking again 10 seconds later silently succeeds. Reproduced on the same Windows machine across 9.0.29 (Freshdesk #173696) and 9.0.30 (#173932). Make the preflight a no-op on non-Linux. Linux keeps the original fast-probe-then-systemdDiag flow unchanged. If we add Windows (`sc query LanternSvc`) or macOS (`launchctl list`) diagnostics later, restore the preflight and call them from here. See getlantern/engineering#3382 for the full archaeology + design discussion. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ffi + lantern-core: bound IPC calls with per-operation timeouts Companion to dropping the non-Linux daemon-reachability preflight in this same PR. The preflight (ffi_nonlinux.go's `checkDaemonReachable`) was introduced in commit bd89bea along with the *removal* of per-call timeouts that used to live on the FFI layer: - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - if err := c.Client().DisconnectVPN(ctx); err != nil { ... } + if err := c.DisconnectVPN(); err != nil { ... } After that change, the only IPC call with any deadline at all was the 300 ms preflight. Every other operation flowed lc.ctx ( context.WithCancel(context.Background())) straight through, meaning a hung lanternd would freeze the UI indefinitely. Dropping the preflight without restoring per-call timeouts removes the only line of defense. Restore them at the LanternCore layer where they belong, with values sized for the inherent work each operation does (state changes can run into multi-second territory; status queries should be near-instant): ipcConnectTimeout = 60 * time.Second // ConnectVPN ipcStateChangeTimeout = 30 * time.Second // SelectServer, DisconnectVPN ipcStatusTimeout = 10 * time.Second // VPNStatus, IsVPNRunning These bound the worst case (hung daemon → user sees a clear error within a minute, no indefinite spinner) without firing during normal slow paths. The dialer's 10 s connect timeout (radiance/ipc/conn_windows.go) already covers the lanternd-crashed case; these guard the lanternd-hung case. vpn_tunnel.{StartVPN, StopVPN, ConnectToServer} take the ctx through their signatures instead of building their own context.Background() internally, so callers stay in charge of their own deadlines. mobile/ mobile.go updated to set 60 s / 30 s / 60 s contexts on its three gomobile entry points. CheckDaemonReachable's 300 ms timeout is kept untouched — Linux still calls it from ffi_linux.go for the systemctl is-active fallback that's the whole point of the fast probe. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Adam Fisk <afisk@mini.local> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ise filter Split out from #8706 — held back from the base-bug fix branch (fisk/fix-empty-apps-base-bug) because this is the heuristic part of that PR, not the actual cause of Freshdesk #173774 / #173778 / #173826. The base bug was GetEnabledApps returning nil-as-"null" instead of empty-as-"[]" (fixed in the other branch); this PR is the broader investigation that grew alongside the diagnosis but stands on its own merits. What's in here: - **HKLM\Software\Microsoft\Windows\CurrentVersion\App Paths scanner.** Apps that register here so they're runnable via Win+R / shellexecute. Catches browsers, IDEs, Office, and most third-party apps that don't go through Squirrel / Start Menu. - **HKLM and HKCU \Software\Microsoft\Windows\CurrentVersion\Run.** Squirrel-managed apps (Slack, Discord, VS Code Insiders) register for auto-start with command lines pointing at Update.exe --processStart. Same parser as Start Menu .lnk targets, including --processStart. - **%LOCALAPPDATA%\<App>\Update.exe pattern.** Belt-and-suspenders for Squirrel installs that don't show in the registry yet but exist on disk under the well-known layout. - **isAppPathsNoise filter.** App Paths is heavily polluted by Microsoft-bundled tooling (IE relics, Office helpers, vestigial Mail + tablet apps) and UWP package plumbing (winget, WindowsPackageManagerServer). Drops entries under known system paths, .NET helper assemblies, anything under \Microsoft Office\ except primary product exes, helper-named basenames (substring match: "browsersupport", "lastpassexporter", "updater", "helper", "diagnostic", "diagcmd"), and basenames suffixed with the generic helper words (update, service, agent, sync, broker). - **ParentKeyName filter removal in isNonUserFacingUninstallEntry.** ParentKeyName != "" was introduced in #8641 and was over-aggressive — plenty of legitimate end-user apps set it (Squirrel installs, winget packages, MSI bundle children). SystemComponent=1 and NoDisplay=1 are the documented signals; those stay. Drops the now-unused parentKeyName field on uninstallEntryMetadata too. - **COM-failure logs in Start Menu scanner promoted Debug → Warn** (CoInitializeEx, WScript.Shell CreateObject, QueryInterface). These paths previously failed silently; now an empty result tells us which call broke. - **Per-filter scan-summary tallies** at the end of each scanner with scanned/kept/per-drop-reason counts and a sample of kept apps for triage. Sample paths are redacted to filepath.Base to avoid including user PII (full Windows paths typically embed the username) in scan logs that get bundled into Report Issue tickets. - **Hot-path constant data hoisted to package scope.** isAppPathsNoise's systemPaths / primaryOfficeExes / helperHints / suffix lists used to be reallocated on every call (runs hundreds- to-thousands of times per scan). Now package-level vars. - **+152 lines of test coverage** for the new heuristics in apps_windows_test.go. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced Apr 28, 2026
…ogging (#8709) Two narrow fixes that together resolve Freshdesk #173774 / #173778 / #173826 (Derek's "Failed to fetch installed apps" empty list on Windows split tunneling). Split out from #8706 so they can land independently of the broader app-discovery rework that PR also contained. 1. **GetEnabledApps returns []string{} instead of nil.** When no apps are split-tunneled, the previous code returned nil, which json.Marshal serialized as "null". Dart's jsonDecode("null") returns null; the receiving code does `as List`, which throws and the UI shows "Failed to fetch installed apps". Initializing as an empty slice serializes to "[]" — Dart parses that as an empty list, no exception, no error UI. THIS is the actual root cause of the empty-list reports we've been chasing; the apps-discovery scanner work was investigating a different (also-real but secondary) issue. 2. **UI-process slog wired up via common.Init.** On the refactor branch, the UI process never called common.Init. slog wrote to stderr (= nowhere on a GUI host), settings were uninitialized, no lantern.log was produced outside the daemon. Patrick caught this — it was a one-line miss in the refactor. Platform-aware so we don't double-init on platforms where the backend embeds in-process: - windows/linux: full common.Init (separate UI + daemon procs) - darwin/ios: setupAppLogging into a distinct lantern-app.log so the main-app slog doesn't race the tunnel extension's lantern.log on lumberjack rotation - android: Mobile.SetupRadiance already ran common.Init upstream — fall through 3. **Auto-attach UI-process *.log to ReportIssue (windows/linux only).** Without it the daemon's archive glob only sees the daemon's logDir; UI-side lantern.log + flutter.log never reach the issue bundle. The daemon runs as SYSTEM on Windows; we keep UI logDir at %PUBLIC%\Lantern\logs so SYSTEM can read it. The broader Windows app-discovery work from #8706 (App Paths scan, Run keys, Squirrel pattern, isAppPathsNoise heuristic filters) is being held in a separate PR for independent review. Co-authored-by: Adam Fisk <afisk@mini.local> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…en't lost (#8711) On Android the entire app runs in a single process, so once common.Init runs slog.SetDefault covers everything. But common.Init only runs deep inside SetupRadiance / StartIPCServer, which LanternVpnService launches asynchronously from an intent fired by MainActivity.startLanternService. Any slog call emitted in the gap — including any of the wide MethodHandler surface that Flutter can reach before the VPN service is up — falls through to the stdlib default (text → stderr → logcat at INFO), so DEBUG logs vanish and the format diverges from what we use everywhere else. Add Mobile.InitLogging as a thin gomobile-exposed wrapper around common.Init, and call it from MainActivity.configureFlutterEngine before startLanternService. common.Init is guarded by an atomic.Bool, so the later call from backend.NewLocalBackend is a no-op. Mirrors PR #8709 (Windows). Reported on Slack by Jigar. Co-authored-by: Adam Fisk <afisk@mini.local> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-refactor # Conflicts: # lib/core/windows/pipe_client.dart # lib/lantern/lantern_ffi_service.dart # lib/lantern/lantern_windows_service.dart
…into fisk/apps-windows-additional-discovery
…itional-discovery # Conflicts: # go.mod # go.sum # lib/features/auth/confirm_email.dart # lib/features/home/provider/app_event_notifier.g.dart # lib/features/home/provider/home_notifier.g.dart # lib/features/logs/provider/diagnostic_log_notifier.g.dart # lib/features/vpn/provider/available_servers_notifier.g.dart # lib/features/vpn/provider/server_location_notifier.g.dart
Contributor
|
LGTM |
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.
Split out from #8706 — the app-discovery rework half. The base-bug fix that resolves the actual "Failed to fetch installed apps" user reports is at #8709 and merges independently of this PR.
What this is, and why it's separate
The original investigation chased an empty Windows split-tunnel apps list. Two distinct issues turned up:
GetEnabledAppsreturningnil(which Dart'sas Listthrows on). One-line fix; lives in lantern-core: fix empty Windows split-tunnel apps list + UI-process logging #8709.The two are unrelated in cause and in code. Reviewing them together (originally as #8706) made it hard to evaluate either piece on its own merits.
Changes
Update.exe --processStart. Same parser as Start Menu.lnktargets.isAppPathsNoisefilter. App Paths is heavily polluted by Microsoft-bundled tooling (IE relics, Office helpers, vestigial Mail + tablet apps) and UWP package plumbing. Drops entries under known system paths,.NEThelper assemblies, anything under\\Microsoft Office\\except primary product exes, helper-named basenames (substring match), and basenames suffixed with generic helper words.ParentKeyNamefilter removed fromisNonUserFacingUninstallEntry. Introduced in Filter system apps from Windows split tunneling #8641 and over-aggressive — plenty of legitimate end-user apps set it (Squirrel installs, winget packages, MSI bundle children).SystemComponent=1andNoDisplay=1are the documented signals; those stay.filepath.Base'd to avoid PII (full Windows paths typically embed the username).isAppPathsNoise'ssystemPaths/primaryOfficeExes/helperHints/ suffix lists were reallocated on every call (runs hundreds-to-thousands of times per scan); now package-level vars.Risk to be aware of
update,service,agent,sync,broker) drops anything ending in those words. A legit user-facing app called "MyApp Service" would be silently hidden from the split-tunnel UI. Tests cover the patterns we thought of; users have unbounded variety. If someone files "my app X is missing from split tunnel" after this lands, that filter is the first place to look.ParentKeyNamefilter removal changes the uninstall-registry filter behavior slightly. The intent is to recover legitimate apps that were being incorrectly hidden; the risk is now seeing some installer subcomponents we didn't see before. The remainingSystemComponent=1/NoDisplay=1filters should still catch most non-user-facing entries.Test plan
gofmtclean.GOOS=windows go vet ./lantern-core/apps/...clean.go test ./lantern-core/apps/...passes locally.🤖 Generated with Claude Code