Skip to content

Stop the automatic update retry loop when the update helper is missing - #133

Merged
Views2k merged 2 commits into
mainfrom
fix/updater-helper-unavailable-retry
Oct 17, 2026
Merged

Views2k merged 2 commits into
mainfrom
fix/updater-helper-unavailable-retry

Conversation

@Views2k

@Views2k Views2k commented Oct 17, 2026

Copy link
Copy Markdown
Owner

Purpose

Wisp Tools (2026-10-17) showed one PC on Wisp 2.6.11 reporting update / install-handoff / failure with ApplicationUpdateHelperUnavailableException every ~2 minutes for more than eight hours (28 events between 01:05 and 02:00 UTC in one session, 251 reports by 09:30). The automatic update check runs every 15 minutes, so something was re-attempting the install handoff far more often than the check itself.

Root cause and solution

Root cause. The 15-minute check is not the loop. AppController.EnableAutomaticApplicationUpdates starts a 5-second DispatcherTimer whose tick runs AutomaticUpdateCoordinator.RunAsync, and the coordinator's apply callback (App.TryBeginApplicationUpdateAsync) returned only bool. When ApplicationUpdateLauncher.Launch → ApplicationUpdateStaging.StageUpdater → RequireUpdaterPath throws ApplicationUpdateHelperUnavailableException because Wisp.Updater.exe is missing from the application directory (most likely quarantined by antivirus), TryBeginApplicationUpdateAsync catches it, records the Tools failure signal, shows the repair message and returns false. The coordinator cannot tell this permanent failure from a transient one, so it sets _retryAfter = now + ApplicationUpdateCheckPolicy.RetryInterval (2 minutes). The downloaded installer stays staged, so PrepareApplicationUpdateAsync returns it immediately, and the first 5-second tick after the 2-minute window repeats the whole handoff: install-handoff attempt, exception, failure report, status flip. That is the ~2-minute cadence seen in Tools.

BeginStartupApplicationUpdateCheck, CheckAndApplyApplicationUpdateAsync, TryAutomaticApplicationUpdateAsync and the cancelled-operation re-arm in the finally of CheckApplicationUpdateAvailabilityAsync all funnel into the same coordinator. None of them is the 2-minute source, but all of them are covered by the change below.

Solution.

  • The apply callback now returns AutomaticUpdateApplyOutcome (Started, Retry, Unavailable) instead of bool.
  • App.TryBeginApplicationUpdateAsync returns ApplicationUpdateStartResult. Its HelperUnavailable flag is set when the start failure is ApplicationUpdateHelperUnavailableException (ApplicationUpdateLauncher.IsHelperUnavailable, which DescribeStartFailure now shares). An implicit conversion from (bool Started, string Error) keeps the existing early returns and ControlPanelWindow.ConfirmApplicationUpdate_Click (.Started, .Error) unchanged.
  • On Unavailable the coordinator sets HandoffUnavailable and ignores every later tick for the rest of the session, including later 15-minute checks and newer releases. A Retry outcome keeps the existing 2-minute bounded retry for transient failures.
  • Only the automatic path is suspended. The dashboard Install update / Try again button still calls TryBeginApplicationUpdateAsync directly and tries once per click, so a user who restores the helper can still update without restarting Wisp.
  • The user-facing message is unchanged: DescribeStartFailure still shows "This copy of Wisp is missing a usable update helper. Download and run the full installer from wispoverlay.com to repair it. Wisp stayed open and no files were installed."
  • Tools now receives one install-handoff attempt/failure pair per session from the automatic path instead of one every two minutes. A manual click still records its own pair because it is a distinct user action.

Validation

Automated, on GitHub CI (windows; the VM cannot run WPF tests). All of these classes are in the PR gate's critical list or selected by the changed files:

  • AutomaticUpdateCoordinatorTests

    • MissingUpdateHelperStopsAutomaticHandoffForTheSession: one download and one handoff, then eight hours of 5-second ticks produce no further download or handoff.
    • MissingUpdateHelperIsNotRetriedForALaterRelease: a newer release found by a later 15-minute check is neither downloaded nor handed off while the helper is missing.
    • TransientHandoffFailureRetriesOncePerRetryInterval: a Retry outcome yields exactly 30 handoff attempts in an hour (first tick, then every 2 minutes), so the bounded retry for transient failures is preserved and the previous cadence is pinned.
    • Existing tests moved to the outcome enum; PendingWorkIsNotInterruptedAndFailureHasBoundedRetry now also asserts HandoffUnavailable stays false.
  • ApplicationUpdateLauncherTests.MissingHelperFailureStopsAutomaticHandoffWhileOtherFailuresRetry: exception classification, unchanged repair message, and the ApplicationUpdateStartResult.Outcome mapping including the tuple conversion.

  • RunsAutosaveTests.UserExitAndUpdaterPrepareMetadataBeforeClosingTheDispatcher: its source-text marker was updated to the new TryBeginApplicationUpdateAsync signature; the ordering it checks is unchanged.

  • git diff --check clean; CI runs dotnet format whitespace --verify-no-changes on the changed files.

  • The change is within the approved scope.

  • The change improves usability, functionality, feel, reliability, or maintenance, or fixes a verified bug.

  • I have read and agree to the contributor-rights terms in CONTRIBUTING.md.

  • Regression coverage has been added or updated where appropriate.

  • Steam and Xbox app / Microsoft Store impacts, checks, and outstanding gaps are recorded separately as required by docs/VALIDATION.md.

  • The complete Release test suite passes.

  • No credentials, personal data, machine paths, game binaries, save data, private captures, or generated output are included, except matching shader bytecode for an approved shader change described in docs/SHADERS.md.

  • User-facing changes are recorded in CHANGELOG.md.

  • Native HUD assets are unchanged, or their distribution rights, provenance, and hashes are documented.

Outstanding validation

  • The complete Release test suite was not run; only the PR gate runs here and the nightly suite covers the rest.
  • No live reproduction on a PC with Wisp.Updater.exe removed. App.TryBeginApplicationUpdateAsync is not unit-testable; its change is the one-line outcome mapping in the existing catch block.
  • No storefront-specific behavior changed (the update path is shared by Steam and Microsoft Store installs), so no separate store checks were recorded.
  • Unchanged, known behavior: while the helper is missing, the 15-minute check still refreshes the status line to "Wisp X is verified and ready to install"; choosing Install update then shows the repair message again.
  • Follow-up candidate, out of scope here: the other non-ready handoff outcomes (HelperExited, TimedOut) still retry every 2 minutes for the whole session. A per-session cap on consecutive automatic handoff failures would bound those too.

Views2k and others added 2 commits October 17, 2026 10:01
The automatic install coordinator treated every failed handoff as transient and re-armed a two-minute retry on its five-second timer. With Wisp.Updater.exe missing from the application directory, each retry threw ApplicationUpdateHelperUnavailableException and reported another install-handoff failure to Tools for the whole session.

The handoff now reports Started, Retry or Unavailable. On Unavailable the coordinator stops automatic attempts for the rest of the session; the repair message is unchanged and Install update still works manually. Tests pin the two-minute cadence for transient failures and the single attempt for a missing helper.
@Views2k
Views2k merged commit 9b81823 into main Oct 17, 2026
10 checks passed
@Views2k
Views2k deleted the fix/updater-helper-unavailable-retry branch October 17, 2026 21:01
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.

1 participant