Repository navigation
Stop the automatic update retry loop when the update helper is missing - #133
Merged
Merged
Conversation
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.
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.
Purpose
Wisp Tools (2026-10-17) showed one PC on Wisp 2.6.11 reporting
update / install-handoff / failurewithApplicationUpdateHelperUnavailableExceptionevery ~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.EnableAutomaticApplicationUpdatesstarts a 5-secondDispatcherTimerwhose tick runsAutomaticUpdateCoordinator.RunAsync, and the coordinator'sapplycallback (App.TryBeginApplicationUpdateAsync) returned onlybool. WhenApplicationUpdateLauncher.Launch→ApplicationUpdateStaging.StageUpdater→RequireUpdaterPaththrowsApplicationUpdateHelperUnavailableExceptionbecauseWisp.Updater.exeis missing from the application directory (most likely quarantined by antivirus),TryBeginApplicationUpdateAsynccatches it, records the Tools failure signal, shows the repair message and returnsfalse. 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, soPrepareApplicationUpdateAsyncreturns it immediately, and the first 5-second tick after the 2-minute window repeats the whole handoff:install-handoffattempt, exception, failure report, status flip. That is the ~2-minute cadence seen in Tools.BeginStartupApplicationUpdateCheck,CheckAndApplyApplicationUpdateAsync,TryAutomaticApplicationUpdateAsyncand the cancelled-operation re-arm in thefinallyofCheckApplicationUpdateAvailabilityAsyncall funnel into the same coordinator. None of them is the 2-minute source, but all of them are covered by the change below.Solution.
applycallback now returnsAutomaticUpdateApplyOutcome(Started,Retry,Unavailable) instead ofbool.App.TryBeginApplicationUpdateAsyncreturnsApplicationUpdateStartResult. ItsHelperUnavailableflag is set when the start failure isApplicationUpdateHelperUnavailableException(ApplicationUpdateLauncher.IsHelperUnavailable, whichDescribeStartFailurenow shares). An implicit conversion from(bool Started, string Error)keeps the existing early returns andControlPanelWindow.ConfirmApplicationUpdate_Click(.Started,.Error) unchanged.Unavailablethe coordinator setsHandoffUnavailableand ignores every later tick for the rest of the session, including later 15-minute checks and newer releases. ARetryoutcome keeps the existing 2-minute bounded retry for transient failures.TryBeginApplicationUpdateAsyncdirectly and tries once per click, so a user who restores the helper can still update without restarting Wisp.DescribeStartFailurestill 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."install-handoffattempt/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:
AutomaticUpdateCoordinatorTestsMissingUpdateHelperStopsAutomaticHandoffForTheSession: 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: aRetryoutcome 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.PendingWorkIsNotInterruptedAndFailureHasBoundedRetrynow also assertsHandoffUnavailablestays false.ApplicationUpdateLauncherTests.MissingHelperFailureStopsAutomaticHandoffWhileOtherFailuresRetry: exception classification, unchanged repair message, and theApplicationUpdateStartResult.Outcomemapping including the tuple conversion.RunsAutosaveTests.UserExitAndUpdaterPrepareMetadataBeforeClosingTheDispatcher: its source-text marker was updated to the newTryBeginApplicationUpdateAsyncsignature; the ordering it checks is unchanged.git diff --checkclean; CI runsdotnet format whitespace --verify-no-changeson 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
Wisp.Updater.exeremoved.App.TryBeginApplicationUpdateAsyncis not unit-testable; its change is the one-line outcome mapping in the existingcatchblock.HelperExited,TimedOut) still retry every 2 minutes for the whole session. A per-session cap on consecutive automatic handoff failures would bound those too.