Skip to content

Map update-check failures to Error, not Idle (SBS-931) - #327

Merged
tsouth89 merged 5 commits into
mainfrom
fix/sbs-931-update-check-idle
Aug 18, 2026
Merged

Map update-check failures to Error, not Idle (SBS-931)#327
tsouth89 merged 5 commits into
mainfrom
fix/sbs-931-update-check-idle

Conversation

@tsouth89

@tsouth89 tsouth89 commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Summary

  • check_for_updates_with_channel now returns Result<Option<UpdateInfo>, UpdateCheckError>. Ok(None) is only a successful latest-is-not-newer. GitHub HTTP errors, transport failures, client-build failures, and unreadable JSON are Err.
  • The desktop command maps Err (and the existing 15s timeout) to UpdateState::Error. Idle is no longer the collapse for we-could-not-tell.
  • About shows AboutUpdateCheckFailed (Could not check for updates.) on Error, not You're up to date. A swallowed invoke failure in checkNow is also Error, so idle+hasChecked cannot claim current.
  • A second check while Ready is skipped so Install and Restart is not cleared when the same version is still latest.

A user who opens Settings, About, clicks Check for updates, and hits a GitHub 403 / outage / unreadable payload now sees Could not check for updates, not You're up to date.

Fixes SBS-931. Not GH 269 (update a11y).

Test plan

  • Settings, About, Check for updates while online and current: still You're up to date
  • Same click with GitHub unreachable or 403: About shows could-not-check, not up-to-date
  • After a download is Ready, Check for updates does not hide Install and Restart
  • Banner Retry still works from an Error state

Required CI gate (from .github/workflows/ci.yml)

Rust / shared (windows-latest): cargo fmt --all --check, cargo test --manifest-path rust/Cargo.toml, cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings

Ran here on Linux:

  • cargo fmt --all --check --manifest-path rust/Cargo.toml: ok
  • cargo test --manifest-path rust/Cargo.toml: 1004 passed, 6 failed
    New SBS-931 tests passed: github_http_error_is_not_a_current_release, unreadable_release_json_is_not_a_current_release, successful_same_version_is_current_not_an_error, successful_newer_release_is_available
    The 6 failures are pre-existing Windows-path / project-name assertions (CI job is windows-latest; this box is Linux).
  • cargo clippy --manifest-path rust/Cargo.toml --all-targets -- -D warnings: fails here on two pre-existing Linux-only lints (CI is windows-latest): rust/src/secure_file.rs unused variable error; rust/src/updater.rs verify_installer_signature_or_delete dead_code (called only under cfg(windows))

Rust / desktop (windows-latest): cargo test + cargo clippy on apps/desktop-tauri/src-tauri/Cargo.toml

Could not compile here: glib-2.0 is not installed (Tauri GTK). Desktop mapping tests (failed_github_check_is_error_not_idle, successful_current_release_is_idle, ready_check_is_skipped_so_install_affordance_stays) will run on the Windows CI job.

Frontend: package-manager binary was Auto-review blocked here. AboutTab + useUpdateState tests are in the diff.

Fail-without-fix

Reverted only interpret_release_http to the old fail-open (HTTP/parse to dummy current release / Idle). The new tests failed:

  • github_http_error_is_not_a_current_release FAILED: 403 Forbidden must be Error, not Idle/current: Ok(GitHubRelease { tag_name: v1.5.33 })
  • unreadable_release_json_is_not_a_current_release FAILED: unreadable JSON must be Error, not Idle/current: Ok(GitHubRelease { tag_name: v1.5.33 })

Restored the production change. Those tests pass with the fix.

Pattern sweep

rg UpdateState::Idle, status === idle, Ok(None), hasChecked

  • updater.rs check + desktop Ok(None) to Idle: Fixed. Failures are Error.
  • About idle && hasChecked to up-to-date: Kept, but Idle is now only a successful current check. Error uses AboutUpdateCheckFailed.
  • useUpdateState.checkNow swallowed invoke errors: Fixed. Catch sets Error.
  • Ready leftover (second check clears installer): Fixed. Ready is skipped like Checking/Downloading.
  • UpdateBanner idle or checking hides: Fine. Idle no longer means failed. Error already shows Retry.
  • Provider incidents Unknown: Already its own state. Failed status-page reads are not Operational; last good incident is kept. Not this ticket.
  • fetch_provider_status Option: Failures omitted from fetch_all_statuses; CLI paints Unknown as ?. Already distinct from Operational. Not this ticket.
  • GH 269: Update a11y announcements. Not this fail-open. Not duplicated.

What this change makes more likely

Error will fire on every GitHub blip, including the 2s startup check. The update banner can now appear at launch when GitHub is down (it previously stayed hidden on Idle). That is the intended direction: unknown is not current. Ready is no longer re-checked, so a user who already has an installer cannot discover a newer newer release until they dismiss. That is the tradeoff that keeps Install and Restart.

Gaps

  • Desktop crate and frontend suite were not run on this Linux box (glib missing; package manager blocked). Windows CI + Frontend CI must carry those.
  • Shared cargo test / clippy were not green here for pre-existing Windows-path tests and Linux-only dead_code. Not introduced by this diff.
  • Timeout / GitHub error strings in the backend are still English; About prefixes them with a localized Could not check for updates.
  • download / apply in useUpdateState still swallow invoke errors. Those paths are not Idle/up-to-date.
  • Did not add a live-network mock of send().await failing; Network is the same Err arm as HTTP/parse in state_from_check_result, and that arm is unit-tested.

Not merged.

Note

Map update-check failures to Error state instead of Idle in the updater

  • Previously, any failed update check (network error, HTTP error, unreadable payload, or timeout) silently resolved to Idle, incorrectly reporting the app as up to date.
  • check_for_updates_with_channel in updater.rs now returns Result<Option<UpdateInfo>, UpdateCheckError>, propagating failures as typed errors with localized user messages.
  • A new state_from_check_result helper in updater.rs maps the result to UpdateState::Error (with message) on failure, Idle only on a confirmed "no newer release", and Available on a newer release.
  • The AboutTab now displays the specific backend error message directly, falling back to a generic "check failed" string when the message is empty.
  • Behavioral Change: failed update checks now surface as errors on the About tab instead of showing "You're up to date!".

Macroscope summarized 74421e3.

A GitHub outage, rate-limit, or unreadable release payload was treated as
"no newer release", so About said the user was current. Idle is only a
successful current check now.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
ceiling 74421e3 Commit Preview URL

Branch Preview URL
Aug 18 2026, 02:23 AM

@coderabbitai

coderabbitai Bot commented Aug 17, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@tsouth89, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 21 minutes

Limit details: You’ve used all 1 included review currently available under your plan.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f5452413-480e-4989-9e51-506c72260a3c

📥 Commits

Reviewing files that changed from the base of the PR and between 9a693f4 and 74421e3.

📒 Files selected for processing (12)
  • CHANGELOG.md
  • apps/desktop-tauri/src-tauri/src/commands/updater.rs
  • apps/desktop-tauri/src/hooks/useUpdateState.test.tsx
  • apps/desktop-tauri/src/hooks/useUpdateState.ts
  • apps/desktop-tauri/src/i18n/keys.ts
  • apps/desktop-tauri/src/surfaces/settings/tabs/AboutTab.test.tsx
  • apps/desktop-tauri/src/surfaces/settings/tabs/AboutTab.tsx
  • rust/src/cli/tty_runner.rs
  • rust/src/locale.rs
  • rust/src/locale/en-US.ftl
  • rust/src/locale/zh-CN.ftl
  • rust/src/updater.rs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 17, 2026

Copy link
Copy Markdown

Automated review

New in this pass: 3 issues.

  1. Timeout error is hardcoded English while other check errors are localized

    apps/desktop-tauri/src-tauri/src/commands/updater.rs:88 · disposition: fix-if-quick · confidence: high · severity: medium · quick win

    state_from_check_result maps Ok(Err()) via error.user_message() which is localized via LocaleKey, but the outer timeout Err() returns "Update check timed out" as a literal. On zh-CN or other locales the user sees English only for timeouts, inconsistent with UpdateErrorNetwork/Http/Parse which are translated. Trigger: any 15s tokio::time::timeout expiry.

    Prompt for AI agents

    In apps/desktop-tauri/src-tauri/src/commands/updater.rs around line 88: Localize the timeout branch the same way: add a locale key (e.g. UpdateErrorTimeout) or reuse AboutUpdateCheckFailed/UpdateErrorNetwork and return get_text(current_language(), key). Add a test that state_from_check_result(Err(elapsed)) message equals the localized string. Verify against the current code first; if no longer valid, skip with a brief reason. Keep the change minimal.

  2. Hardcoded /bin/sh breaks on systems without /bin/sh

    rust/src/cli/tty_runner.rs:717 · disposition: fix-if-quick · confidence: high · severity: medium · quick win

    The Windows fix correctly uses COMSPEC absolute path to avoid npm shim shadowing, but the Unix branch was also changed from "sh" (PATH-resolved) to "/bin/sh". On NixOS, minimal containers, or systems where sh lives at /usr/bin/sh, the PTY test and any caller using tty_runner will fail to spawn. Trigger: run cargo test on a system where /bin/sh does not exist but sh is on PATH.

    Prompt for AI agents

    In rust/src/cli/tty_runner.rs around line 717: Keep Windows absolute (COMSPEC) but restore PATH lookup on Unix: use "sh" or try "/bin/sh" with fallback to "sh". If absolute is required, probe both /bin/sh and /usr/bin/sh. Verify against the current code first; if no longer valid, skip with a brief reason. Keep the change minimal.

  3. UpdateCheckError::Empty is defined but never constructed

    rust/src/updater.rs:92 · disposition: fix-if-quick · confidence: high · severity: low · quick win

    The enum adds Empty for beta listing with no non-draft release, but interpret_release_http returns Ok(None) for empty [] and draft-only listings, never Err(Empty). The variant is dead code and its user_message mapping (to UpdateErrorParse) is misleading. Trigger: any empty beta listing exercises Ok(None) path, Empty is unreachable.

    Prompt for AI agents

    In rust/src/updater.rs around line 92: Remove the Empty variant or use it (and decide its user_message). If Ok(None) is intentional for empty beta, delete Empty and update docs. Verify against the current code first; if no longer valid, skip with a brief reason. Keep the change minimal.

Resolved since the previous pass: 6.

For coding agents: fix BLOCK and FIX IF QUICK findings now; everything else is tracked or informational; never exceed one CodeRev fix round per PR.

Advisory. Findings generated by grok-subscription and muse-spark-1.2-contributor, each filtered through a 3-vote refutation panel with the changed code in evidence.

Comment thread apps/desktop-tauri/src/surfaces/settings/tabs/AboutTab.tsx Outdated
Comment thread apps/desktop-tauri/src/hooks/useUpdateState.ts Outdated
Comment thread rust/src/updater.rs
Comment thread apps/desktop-tauri/src/surfaces/settings/tabs/AboutTab.tsx
Four things the review found, all in how a failed update is reported.

An empty beta listing was an error. A repository with nothing published
yet, or only drafts, is a successful "no release to offer", and calling
it a failure put "Could not check for updates" on About for a channel
that was simply empty. It now reads as Idle.

About prefixed every error with the check-failure sentence. Download and
apply set their own errors, so a hash mismatch or a rejected signature
read as a failed check, and a real check failure read the sentence
twice. The backend message now stands alone, with the prefix kept only
as the fallback for an error with no message of its own.

That message was English glued onto a localized prefix. The three
check-failure sentences are now locale keys in both languages, so the
whole line is translated rather than half of it.

A failed check no longer wipes a downloaded update off the screen. The
catch reset the surface to idle-plus-error, which took Install and
Restart away from an installer the backend still held on disk.
The three update-error keys were added to locale.rs and both .ftl files
but not to the frontend key list, which the build cross-checks. That is
what failed the Frontend job.

The PTY test asked for a bare "cmd". That resolves through the enriched
PATH, which puts the npm directory ahead of System32, so a machine
carrying an extensionless npm shim named `cmd` launched a shell script
through CreateProcessW and failed with "not a valid Win32 application".
It now names the shell by COMSPEC, which is what the test meant. This is
the failure that has been red on this branch and intermittently
elsewhere; the shared suite is green with it.
Comment thread apps/desktop-tauri/src-tauri/src/commands/updater.rs Outdated
Comment thread rust/src/cli/tty_runner.rs
Comment thread rust/src/updater.rs
Three from the latest review.

The timeout was the one check failure still hardcoded in English while
its neighbours went through the locale. It has a key now, in both
languages.

The PTY test needed an absolute shell on Windows to dodge an npm shim,
but making the Unix branch absolute too traded one machine-specific
assumption for another: sh is not always at /bin/sh. Unix goes back to
the PATH lookup.

UpdateCheckError::Empty had no constructor left once an empty beta
listing stopped being an error, so it is gone rather than sitting there
implying a state that cannot happen.
@tsouth89
tsouth89 merged commit cafd020 into main Aug 18, 2026
10 of 11 checks passed
@tsouth89
tsouth89 deleted the fix/sbs-931-update-check-idle branch August 18, 2026 02:26
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