Repository navigation
Stop automatic update retries when the update helper rejects the installation and report its code - #139
Merged
Conversation
…allation and report its code When the helper exits before it is ready, read the result file it wrote, report its error code to Tools under the install-handoff stage, and treat InvalidRequest exit codes and installation-identity failures as permanent for the session, the way a missing helper already is. Show reinstall instructions when the registered install location does not match. The dashboard button still tries once per click.
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-19) shows one PC on Wisp 2.6.12 that cannot update to 2.6.13. Its update helper exits with
UPDATE_INSTALL_REGISTRATION("The installed Wisp location could not be verified") while Wisp is still open. Wisp reportsupdate / install-handoff / failurewith the generic reasonupdate-verification-failedand retries every two minutes for as long as it runs (100 failures since 2026-10-18 02:38 UTC). Each launch then consumes the helper's result file and reports the same failure once more under theupdate-installedstage with recoveryapplication-still-running.#133 (shipped in 2.6.13) stops the automatic retries only when
Wisp.Updater.exeis missing. A helper that starts and rejects the install was still aRetryoutcome.Root cause and solution
Root cause.
App.TryBeginApplicationUpdateAsynctreated everyHelperExitedhandoff state the same way: a fixedupdate-verification-failedincident, a fixed message, and a plain(false, error)result that maps toAutomaticUpdateApplyOutcome.Retry. The helper's own result file (%LOCALAPPDATA%\Wisp\update-result.json, written before it exits) was never read at that point, so neither the reported reason nor the retry decision knew which check had failed. The registration check compares the HKCU uninstall key'sInstallLocationplusWisp.exewith the running executable's path; that cannot change while this Wisp process runs, so retrying within the session cannot succeed.Solution.
ApplicationUpdateLauncher.DescribeHelperExitreads the helper's result file when the helper exits before signalling Ready. Afailedrecord written during this handoff (itsrecordedAtUtcwithin a minute of the launch) is consumed, and itserrorCodebecomes the Tools incident reason in the catalog's kebab-case form (for exampleupdate-install-registration) with failure stageupdate-install-handoff, the record'snativeErrorCodeas the code and its recovery state as update evidence. An older or unreadable record is left for the next launch to consume, and only the helper's exit code is reported (reasonupdate-unexpected-failure, code = exit code). No text from the record is rendered or sent.InvalidRequestexit code (now the shared constantUpdateApplyContract.InvalidRequestExitCode, whichUpdaterExitCode.InvalidRequestreferences), or one of the installation-identity reasonsupdate-install-registration,update-application-path,update-installed-app-invalid.ApplicationUpdateStartResult.HelperRejectedmaps them toAutomaticUpdateApplyOutcome.Unavailable, the latch Stop the automatic update retry loop when the update helper is missing #133 added for the missing helper: no further automatic handoff attempts until the next Wisp launch. Other codes (installer validation, parent-process checks, a crash without a record) keep the bounded two-minute retry.TryBeginApplicationUpdateAsyncdirectly, one attempt per click. Once the automatic path has stopped, the 15-minute check shows "Wisp X could not be installed automatically. Download and run the full installer from wispoverlay.com." instead of "verified and ready to install" (the gap noted in Stop the automatic update retry loop when the update helper is missing #133).update-installedfailure report at the next launch for this case. Records from helpers that ran after Wisp exited are still consumed at launch as before.TryReadRecordis shared byTryConsumeResultandDescribeHelperExit. It now checks thatschemaVersionis a JSON number before reading it; a string value would previously have thrown out ofTryConsumeResultat startup.SupplementaryIncidentSchema.Registry lookup stays per-user
The HKCU-only lookup in
WindowsInstallationRegistrationReaderis kept, with the exact path-equality check. Evidence:installer/Wisp.isssetsPrivilegesRequired=lowestwithoutPrivilegesRequiredOverridesAllowed, so Setup always runs in non-administrative install mode and writes the{A8FC0D58-11E3-4B25-B78D-3B98E9855473}_is1uninstall key under HKCU;DefaultDirName={localappdata}\Programs\Wisp; the script's ownExistingInstallationPresentreads HKCU./CURRENTUSER(UpdaterConstants.InstallerArguments), so an update never creates an HKLM registration either. The private test-installer script changes only the display/output version and source paths, not the AppId or privileges. Reading HKLM would only ever match a registration the official installer never writes./WISPUPDATEis treated as a fresh install that re-runs the setup wizard.InstalledApplicationValidatorTestsnow pin the semantics: a trailing separator and case differences are accepted; a missing, empty or relativeInstallLocation, or a different folder, is rejected asUPDATE_INSTALL_REGISTRATIONwith theInvalidRequestexit code.Remaining hypothesis for the affected PC, not verifiable from Tools data:
UpdatePathSafety.PathsEqualnormalises case and trailing separators but does not expand 8.3 short names or resolve junctions, so a Wisp started through a short-name path or a redirected profile folder compares unequal to the registered long path. The specific code now reaching Tools confirms which check fails; the path shape needs diagnostics from that PC.Validation
Run on a Windows PC from a snapshot of this branch head (.NET SDK 8.0.425, Release build):
Wisp.App.Testsfiltered toApplicationUpdate*,AutomaticUpdateCoordinatorTests,SupplementaryIncident*,SupplementaryOperationFailureTests,AppControllerLifecycle*andRunsAutosaveTests.UserExitAndUpdaterPrepareMetadataBeforeClosingTheDispatcher: 155 passed.ApplicationUpdateLauncherTests:HelperExitRecordNamesTheFailedCheckAndStopsTheSessionWhenItCannotChange(seven codes: reason, stage, code, recovery, schema validity, no record text in the incident or message, permanence per code, record consumed),HelperExitWithoutARecordReportsTheExitCodeAndStopsOnlyForARejectedRequest,StaleHelperRecordIsLeftForTheNextLaunch,UnreadableHelperRecordDoesNotBreakTheReport;MissingHelperFailureStopsAutomaticHandoffWhileOtherFailuresRetrycovers theHelperRejectedmapping.AutomaticUpdateCoordinatorTests.RejectedHandoffAfterTransientRetriesStopsForTheSession: two retries, then the rejection; eight hours of five-second ticks and a later 15-minute check add no attempts.Wisp.Updater.Tests: 75 passed, including the newInvalidRequestExitCodeIsTheSharedContractValue,RegistrationComparesTheFullPathIgnoringCaseAndTrailingSeparatorandMissingOrRelativeRegistrationIsRejectedAsAnInvalidRequest; the result-file test now also checksrecordedAtUtc.Wisp.Update.Tests: 144 passed.dotnet format whitespace --verify-no-changeson the changed files,Test-RepositoryPolicy.ps1andgit diff --check: clean.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
App.TryBeginApplicationUpdateAsyncis not unit-testable; its change is the branch that callsDescribeHelperExitand returnsHelperRejected.install-handofffailure arrives with the specific code.