Add installer diagnostics guide for triaging update issues#47105
Add installer diagnostics guide for triaging update issues#47105LegendaryBlair wants to merge 3 commits intomainfrom
Conversation
Step-by-step guide for diagnosing old update installer accumulation, covering log files to check, UpdateState.json interpretation, and common root causes.
There was a problem hiding this comment.
Pull request overview
Adds a developer-facing troubleshooting guide to help triage PowerToys installer/update issues—especially cases where old installers accumulate under the per-user Updates folder.
Changes:
- Introduces a new diagnostics doc with key file paths (UpdateState.json, Runner/Update logs, Updates folder).
- Documents
UpdateState::Stateenum values and a step-by-step triage flow for installer accumulation scenarios. - Lists common root causes and log patterns to look for during investigation.
| - **state = 3 (readyToInstall)**: An installer is downloaded but never installed. Cleanup at startup is skipped (by design, to preserve the pending installer). If this state persists across many update cycles, old files can accumulate on versions < v0.73.0. | ||
| - **state = 1 (errorDownloading)**: A previous download or install failed. Cleanup should run at next startup (v0.73+). | ||
| - **state = 2 or 4**: Transient states. Cleanup should run at next startup (v0.73+). |
There was a problem hiding this comment.
The guidance for when cleanup runs is inaccurate. Runner startup cleanup is gated on UpdateState::upToDate (see src/runner/main.cpp), and updating::cleanup_updates() is otherwise only invoked right before downloading a new installer (see src/runner/UpdateUtils.cpp / src/Update/PowerToys.Update.cpp). Please reword these bullets to avoid implying cleanup runs automatically at the next startup for non-upToDate states.
| - **state = 3 (readyToInstall)**: An installer is downloaded but never installed. Cleanup at startup is skipped (by design, to preserve the pending installer). If this state persists across many update cycles, old files can accumulate on versions < v0.73.0. | |
| - **state = 1 (errorDownloading)**: A previous download or install failed. Cleanup should run at next startup (v0.73+). | |
| - **state = 2 or 4**: Transient states. Cleanup should run at next startup (v0.73+). | |
| - **state = 3 (readyToInstall)**: An installer is downloaded but never installed. Cleanup at startup is skipped (by design, to preserve the pending installer). On v0.73+, cleanup can still occur later if a future update flow reaches the pre-download cleanup path before fetching another installer. | |
| - **state = 1 (errorDownloading)**: A previous download or install failed. Do not expect automatic cleanup on the next startup; on v0.73+, cleanup is performed before a new installer download is attempted. | |
| - **state = 2 or 4**: No automatic cleanup is expected on the next startup unless the state returns to `upToDate`; on v0.73+, cleanup otherwise happens when a new installer download is about to start. |
|
|
||
| ### Step 3: Check if PowerToys.Update.exe has ever run | ||
|
|
||
| - **UpdateLogs directory missing**: `PowerToys.Update.exe` was never launched. The user never triggered an install — either they dismissed all update notifications, or Stage 1 failed before Stage 2 could run. |
There was a problem hiding this comment.
The statement "UpdateLogs directory missing: PowerToys.Update.exe was never launched" is too strong. PowerToys.Update.exe does not explicitly create the UpdateLogs subfolder before initializing the logger (it only ensures the root save folder exists), so missing logs could also mean logging initialization failed or logs were removed. Suggest rewording this as a possible indicator rather than a definitive conclusion.
| - **UpdateLogs directory missing**: `PowerToys.Update.exe` was never launched. The user never triggered an install — either they dismissed all update notifications, or Stage 1 failed before Stage 2 could run. | |
| - **UpdateLogs directory missing**: This suggests `PowerToys.Update.exe` may never have been launched, or may not have progressed far enough to create logs. It can also mean logging initialization failed or the logs were later removed. The user may never have triggered an install, or Stage 1 may have failed before Stage 2 could run. |
| |---|---| | ||
| | `Failed to delete installer file ... Access is denied` | File locked by AV, another process, or permissions issue | | ||
| | `Failed to delete log file ...` | Same, for old log files | | ||
| | `Failed to clean up old update files:` | Exception in cleanup (v0.73+ with exception handling) | |
There was a problem hiding this comment.
Failed to clean up old update files: does not appear to be an emitted log message in the current cleanup implementation; updating::cleanup_updates() logs per-file failures as Failed to delete installer file ... / Failed to delete log file .... Please remove or replace this pattern with log strings that actually exist so triage steps don't send people searching for non-existent messages.
| | `Failed to clean up old update files:` | Exception in cleanup (v0.73+ with exception handling) | |
| | No cleanup-related entries at all | Cleanup was never called — likely state gate blocked it | | ||
|
|
||
| ### Step 5: Check the Updates folder contents | ||
|
|
||
| - **All different versions**: Cleanup never ran across multiple update cycles. Points to state gate issue or pre-v0.73 binary. |
There was a problem hiding this comment.
"No cleanup-related entries at all" isn't a reliable signal that cleanup was never called: updating::cleanup_updates() only logs when deletions fail, and otherwise is silent. Consider pointing readers to call-site logs (e.g., the download trace lines) or to check the Updates folder state rather than relying on absence of cleanup logs.
| | No cleanup-related entries at all | Cleanup was never called — likely state gate blocked it | | |
| ### Step 5: Check the Updates folder contents | |
| - **All different versions**: Cleanup never ran across multiple update cycles. Points to state gate issue or pre-v0.73 binary. | |
| | No cleanup-related entries at all | Inconclusive by itself: `cleanup_updates()` is silent on success. Corroborate with call-site runner log entries (for example `Discovered new version` and download-related traces) and with the `%LOCALAPPDATA%\Microsoft\PowerToys\Updates\` folder state in Step 5. | | |
| ### Step 5: Check the Updates folder contents | |
| - **All different versions**: Cleanup likely did not run across multiple update cycles, or an older/pre-fix binary is in use. Confirm with runner log call-site traces and update state before concluding a state gate issue. |
- Fix incorrect claim that cleanup runs at startup for non-upToDate states; clarify it runs at pre-download step on v0.73+ - Soften UpdateLogs missing claim from definitive to suggestive - Remove non-existent log message 'Failed to clean up old update files' - Clarify that absence of cleanup logs is inconclusive since cleanup is silent on success
| | File/Folder | Path | Contains | | ||
| |---|---|---| | ||
| | UpdateState.json | `%LOCALAPPDATA%\Microsoft\PowerToys\UpdateState.json` | Persisted update state machine | | ||
| | Runner logs | `%LOCALAPPDATA%\Microsoft\PowerToys\RunnerLogs\runner-log_*.log` | Startup, update checks, cleanup | | ||
| | Update logs | `%LOCALAPPDATA%\Microsoft\PowerToys\UpdateLogs\update-log_*.log` | PowerToys.Update.exe activity | | ||
| | Updates folder | `%LOCALAPPDATA%\Microsoft\PowerToys\Updates\` | Downloaded installer files | |
There was a problem hiding this comment.
Markdown table syntax is incorrect: each row starts with ||, which typically renders as an extra empty column (or a malformed table) in many Markdown renderers. Use a single leading | for each row (and alignment row) so the table renders correctly.
| | Value | Name | Meaning | | ||
| |---|---|---| | ||
| | 0 | upToDate | No update needed | | ||
| | 1 | errorDownloading | Download or install failed, will retry | | ||
| | 2 | readyToDownload | New version found, not yet downloaded | | ||
| | 3 | readyToInstall | Installer downloaded, waiting for user action | | ||
| | 4 | networkError | GitHub API call failed | |
There was a problem hiding this comment.
This table has the same || issue as above and is likely to render incorrectly. Also, since these values are sourced from an enum in code, consider adding an 'As of version/commit' note here to reduce future drift if the enum changes.
| ```json | ||
| {"state": 3, "downloadedInstallerFilename": "powertoyssetup-0.98.1-x64.exe", ...} |
There was a problem hiding this comment.
The code fence is marked as json, but the example isn’t valid JSON due to the .... This can mislead readers and can break tooling that validates/pretty-prints JSON snippets. Consider either removing the ellipsis and keeping the example valid JSON, or switching the fence to jsonc/text and explicitly noting fields are omitted.
| ```json | |
| {"state": 3, "downloadedInstallerFilename": "powertoyssetup-0.98.1-x64.exe", ...} | |
| ```jsonc | |
| {"state": 3, "downloadedInstallerFilename": "powertoyssetup-0.98.1-x64.exe" /* additional fields may be present */} |
| | Log pattern | Meaning | | ||
| |---|---| | ||
| | `Failed to delete installer file ... Access is denied` | File locked by AV, another process, or permissions issue | | ||
| | `Failed to delete log file ...` | Same, for old log files | | ||
| | `Discovered new version` | Periodic update check ran | | ||
| | `New version is already downloaded` | State is `readyToInstall` and filename matches — no re-download, no cleanup | | ||
| | No cleanup-related entries at all | Inconclusive by itself — `cleanup_updates()` is silent on success. Corroborate with the Updates folder contents (Step 5) and the running version (Step 1). | |
There was a problem hiding this comment.
This Markdown table also starts rows with ||, which can cause incorrect rendering. Using a single leading | per row will keep the table readable in GitHub and other renderers.
| | Root cause | Evidence | Fix | | ||
| |---|---|---| | ||
| | Running pre-v0.73.0 binary | `product_version` < v0.73.0 in runner log | Manually upgrade to latest | | ||
| | State stuck at `readyToInstall` (pre-v0.73) | `state: 3` in UpdateState.json, no UpdateLogs | Manually upgrade to latest | |
There was a problem hiding this comment.
Same || table formatting problem here. Additionally, the Evidence cell uses state: 3 while earlier examples use \"state\": 3; keeping the JSON key format consistent (and valid JSON) will reduce confusion for readers extracting the value from UpdateState.json.
| | State stuck at `readyToInstall` (pre-v0.73) | `state: 3` in UpdateState.json, no UpdateLogs | Manually upgrade to latest | | |
| | State stuck at `readyToInstall` (pre-v0.73) | `"state": 3` in UpdateState.json, no UpdateLogs | Manually upgrade to latest | |
Step-by-step guide for diagnosing old update installer accumulation, covering log files to check, UpdateState.json interpretation, and common root causes.
Summary of the Pull Request
PR Checklist
Detailed Description of the Pull Request / Additional comments
Validation Steps Performed