Repository navigation
fix(desktop): roll back a failed update swap and accept only this repo's assets - #891
Merged
Merged
Conversation
…o's assets A failed rename in the middle of the in-app update swap left the install with no backend/, and the next launch's sweep deleted backend.old, the only copy, so the app needed a full re-download. The swap now undoes the renames already done when one fails, the sweep keeps backend.old while there is no backend/app, and the rename retry budget is about 5 s (a missing path fails at once). The update URL check accepted any github.com path, and the checksum and runtime id fetches had no check at all, so the page could point the updater at another repository's release. All three now require https://github.com/stemdeckapp/stemdeck/releases/download/. Release builds no longer take the app root from the working directory, so a portable exe started inside a source checkout runs its own install.
thcp
added a commit
that referenced
this pull request
Nov 3, 2026
…ision (#895) Three small repairs to how the desktop shell swaps torch for a CUDA build and checks torchvision on launch. Builds on #891. ## What changes for the user - **A CUDA install that verified stays put.** After a good CUDA swap, the shell deletes the backup of the bundled CPU torch. If that delete stopped part way (a file held open, for example by an antivirus scan), the half-deleted backup still had its `manifest.txt`, so the next launch treated it as an interrupted swap and restored half a CPU torch over the working CUDA one. The commit now removes `manifest.txt` first, so a delete that stops part way leaves nothing that counts as a backup. The next snapshot clears that manifest-less leftover before it moves anything, so a stale entry cannot block the move or be restored later. - **A damaged RECORD or manifest cannot point outside its folder.** `owned_entries` takes the first path segment of each line in torch's `RECORD` and kept it if `site.join(name).exists()`. On Windows that is true for names that are not entries at all: `C:` (from a line such as `C:\x,,`) joins to that drive's current directory, and `...` or `. ` are trimmed to site-packages itself. `discard_installed` would then delete them. A name is now kept only if `read_dir(site)` lists it, which never returns any of those. `restore` uses the same rule for the names in `manifest.txt`, matched against `read_dir` of the backup, because a manifest written by an older build from such a RECORD could hold them. - **A missing torchvision is put back.** On a CPU torch install, the launch check only repaired a CUDA torchvision; a torchvision that was gone (a pip run that stopped between uninstall and install) was reported as healthy, and the karaoke split failed at import every time. A missing `torchvision/version.py` beside `+cpu` torch now gets the same CPU torchvision install. A `version.py` that exists but cannot be read is still left alone, so a healthy offline machine is not sent to the network on every launch. A CUDA torch is not touched. ## How it was verified - Red then green on Windows, `cargo test`: - `torchswap::tests::a_commit_that_stops_part_way_leaves_no_backup_to_restore` (Windows only: it holds a backup file open with `share_mode(0)`). Before: `assertion failed: !has_backup(&site)`. With only the commit change, the second `snapshot` failed on the leftover. After: passes. - `torchswap::tests::a_record_line_naming_no_entry_in_site_is_not_owned`. Before: owned entries included `"C:"`, `"..."`, `". "` and `".. "`. After: passes. The test only reads, so running it against the old code was safe. - `torchswap::tests::a_manifest_line_that_names_no_entry_is_ignored` (manifest lines `...` and `. `). Before: `restore` removed the test's site-packages and then failed. After: passes, CPU torch and the neighbour package are intact. It works in a temp dir, so the red run could only delete that. - `tests::a_missing_torchvision_beside_cpu_torch_is_repaired` and the extended `tests::the_repair_decision_reads_the_installed_tree`. The helper's signature changed to take `Option<&str>`, so the old code cannot compile these; the old behaviour is visible in the removed `let (Some(torch), Some(vision)) = ... else { return "ok" }`. - The manifest test has no `C:` line on purpose: against the old code it would make `restore` delete from the drive's current directory, so a red run is not safe on any machine. The same `read_dir` rule covers it. - Full `cargo test`: 209 passed, 0 failed, 2 ignored. `cargo fmt --check` and `cargo clippy --tests -- -D warnings` clean. - No Python, frontend or i18n change. No new control, so no Guide entry. ## Update path - This changes the shell binary only. `uv.lock` is unchanged, so the `runtimeId` gate is not affected and existing installs receive it through the in-app update. ## What I have to check by hand On a Windows machine with an NVIDIA GPU, using a throwaway install of the desktop build: 1. Run setup so the CUDA torch installs and verifies. Confirm `setup.log` says `verified` and that `.stemdeck-torch-backup` next to `site-packages` is gone. 2. On a CPU install, delete `site-packages/torchvision` and `torchvision-*.dist-info`, then launch. Confirm `setup.log` says `torchvision is missing beside torch ...; installing torchvision==...+cpu`, and that the karaoke split works afterwards. 3. Launch again and confirm no second repair runs.
This was referenced Nov 3, 2026
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.
Three fixes to the desktop in-app updater and app-root lookup (Windows and Linux updater; the app-root change applies to every desktop build). All in
desktop/src-tauri/src/main.rs.What changes for the user
backend/tobackend.old, then the newbackend/into place, then swaps the exe. If any of those renames fails (for example a virus scanner holding a handle), the renames already done are now undone, newest first, so the previous install is left exactly as it was. Before, a failure on the second rename left nobackend/at all, and the next launch's cleanup deletedbackend.old, the only copy, so the install needed a full re-download.backend.oldwhen there is nobackend/app. Then it is the only copy of the app (a swap that stopped between its renames, for example a power cut), so it is left on disk instead of deleted.github.compath, so a script in the page could hand the updater a release archive from another repository, together with that repository's own checksum. It now requireshttps://github.com/stemdeckapp/stemdeck/releases/download/. The two small files read before an update (runtime-version.jsonand the.sha256that the archive is checked against) now go through the same check; before, they had no check at all.objects.githubusercontent.comis no longer accepted as a starting URL: the frontend always passes the releases API'sbrowser_download_url(verified against the v0.20.0 and v0.19.1 asset lists), and GitHub's redirect to its asset host is followed by the HTTP client, not by this check.StemDeck.exestarted from a terminal inside a source checkout ran that checkout'sapp/instead of its own install. Only debug builds (cargo tauri dev) look at the working directory now. A release build inside the repo'starget/still finds the checkout through the exe's own path, as before.How it was verified
origin/big-releasefor the stated reason and passing with the change:the_launch_sweep_keeps_backend_old_when_it_is_the_only_backend(before:backend.old/appdeleted)a_failed_backend_install_puts_the_previous_backend_back(before:backend/missing after the failed rename)only_our_own_release_assets_are_downloadable_as_updates, extended with another repository, a lookalike repo name, a dot-segment path, a non-release path of this repo and an asset-host URL (before:https://github.com/someone/stemdeck/releases/download/v1/x.zipaccepted)update_checksum_and_runtime_id_fetches_are_held_to_the_same_urls(before: the small-file fetch went to the network)only_debug_builds_take_the_app_root_from_the_working_directory(before: the checkout was returned with the release flag)a_failed_exe_install_puts_the_previous_backend_and_exe_back(a failure at the last rename undoes all four renames).cargo fmt --check,cargo clippy --tests -- -D warningsandcargo test(205 passed) pass on Windows. The Linux and macOS compile is left to the PR's Rust check jobs (no platform-only block was changed).Update path
uv.lock, so the runtime id is unchanged.Known gaps, not changed here
download_app_updatestill takes the expected checksum from the page, so a script in the page could install an older release of this repository. It can no longer point at another repository.What to verify by hand on an installed build
refusing to download an update from. The DevTools console line[catalog] in-app update unavailable:shows the exact reason.backend.oldandStemDeck.exe.oldare gone from the install folder.cdinto a StemDeck source checkout, and start the installedStemDeck.exeby its full path. Expected: it opens the installed app with its own settings and library location, not the checkout.