Skip to content

fix(updater): ignore stale check results#330

Open
ThunderTr77 wants to merge 4 commits into
TouchAI-org:mainfrom
ThunderTr77:fix/updater-stale-check-race
Open

fix(updater): ignore stale check results#330
ThunderTr77 wants to merge 4 commits into
TouchAI-org:mainfrom
ThunderTr77:fix/updater-stale-check-race

Conversation

@ThunderTr77

@ThunderTr77 ThunderTr77 commented May 31, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes a stale updater race where in-flight update checks could finish after the user changed channels or after a newer check completed.

  • Adds a request version guard to AppUpdateController checks.
  • Invalidates pending check results when the update channel changes.
  • Prevents stale successes from persisting lastCheckedAt and stale failures from restoring old state.
  • Covers out-of-order same-channel checks so older results cannot overwrite newer results.

Related issue or RFC

AI assistance disclosure

  • Tool(s) used: local development assistant
  • Scope of assistance: bug discovery, regression tests, local patch, validation, and PR preparation
  • Human review or rewrite performed: reviewed duplicate search results, diff, and command outputs before submission
  • Architecture or boundary impact: no boundary changes; controller-level stale request guard only

Testing evidence

  • corepack pnpm --dir apps/desktop exec vitest run --configLoader runner tests/services/AppUpdateService/service.test.ts tests/services/AppUpdateService/state.test.ts passed: 2 files, 26 tests
  • corepack pnpm --dir apps/desktop exec eslint src/services/AppUpdateService/index.ts tests/services/AppUpdateService/service.test.ts passed
  • corepack pnpm --dir apps/desktop exec prettier --check src/services/AppUpdateService/index.ts tests/services/AppUpdateService/service.test.ts passed
  • corepack pnpm --dir apps/desktop run test:typecheck passed
  • git diff --check passed
  • Full pnpm test:pr was not run locally; CI is covering the broader matrix.

Risk notes

  • AgentService, runtime, MCP, or schema impact: none.
  • database baseline or migration impact: none.
  • release or packaging impact: low; updater state handling only, no packaging scripts changed.

Screenshots or recordings

No UI change; not applicable.

Checklist

  • The PR title follows Conventional Commits and is valid for squash merge.
  • This PR is either ready for review or explicitly marked as a Draft PR.
  • I did not use [WIP] or similar title prefixes.
  • If AI materially assisted this PR, I disclosed the tools and scope and I personally reviewed every affected change.
  • I can explain the why, what, and how of this change without relying on an AI tool.
  • If this touches AgentService, runtime, MCP, or schema boundaries, there is an accepted RFC.
  • If this changes architecture or adds a new cross-boundary abstraction, there is an accepted RFC.
  • I ran pnpm test:pr for this code PR, or this is a docs-only change.
  • If I changed Rust behavior or tests, I reviewed pnpm test:coverage:rust or relied on CI coverage evidence.
  • If I changed desktop startup/window/search/popup/settings/E2E paths, I ran pnpm test:e2e locally or documented why CI is the first valid proof.
  • I added tests or explained why tests are not appropriate.
  • I updated docs when behavior changed.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @ThunderTr77, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@coderabbitai

coderabbitai Bot commented May 31, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: bc0a0d81-f2a3-4ba6-b6b3-5a5fb3d86bd3

📥 Commits

Reviewing files that changed from the base of the PR and between 54f738e and 9ba57af.

📒 Files selected for processing (2)
  • apps/desktop/src/services/AppUpdateService/index.ts
  • apps/desktop/tests/services/AppUpdateService/service.test.ts
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: Rust Checks
  • GitHub Check: Frontend Quality
  • GitHub Check: CodeQL (javascript-typescript)
  • GitHub Check: CodeQL (rust)
  • GitHub Check: Desktop E2E Smoke (Windows)
🔇 Additional comments (2)
apps/desktop/src/services/AppUpdateService/index.ts (1)

113-119: LGTM!

apps/desktop/tests/services/AppUpdateService/service.test.ts (1)

51-52: LGTM!

Also applies to: 70-72, 311-341


📝 Walkthrough

Summary by CodeRabbit

Bug Fixes

  • Fixed app update checks to properly handle channel changes while checks are in progress, preventing stale results from incorrectly updating the app state.
  • Improved handling of overlapping manual update checks to ensure only the most recent result is applied.

Walkthrough

AppUpdateController adds per-request versioning to ensure only the latest check for the active channel can commit completion/failure or persist lastCheckedAt. Tests and test helpers were expanded to cover channel-switch races, persistence failures, and overlapping checks.

Changes

Stale Update Check Prevention

Layer / File(s) Summary
Request versioning implementation
apps/desktop/src/services/AppUpdateService/index.ts
Adds checkRequestVersion, increments on channel change and per checkNow call, captures a local requestVersion in checkNow, uses isCurrentCheckRequest to short-circuit stale success/error paths, and prevents stale results from mutating state or persisting lastCheckedAt.
Race and persistence test coverage
apps/desktop/tests/services/AppUpdateService/service.test.ts
Adds createController updateChannelError option and multiple tests: stale automatic check after channel switch, stale manual check after switch, setChannel persistence failure during in-flight check, and overlapping manual checks where the newer result wins.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 In the meadow of checks a number was sewn,
Each request got a badge, each old one outgrown.
When channels jump sideways and races ensue,
Only the freshest result gets to break through.
Hop, skip, persist—now the timeline is true.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title 'fix(updater): ignore stale check results' follows Conventional Commits, is concise, and accurately describes the primary change of adding stale request guards to prevent outdated update checks from mutating state.
Description check ✅ Passed Description includes all required sections: summary of changes, linked issue (#329), AI assistance disclosure with tool scope, testing evidence with specific commands, risk assessment, and completed checklist.
Linked Issues check ✅ Passed The PR implementation meets all coding requirements from issue #329: adds request version tracking to guard stale checks [#329], invalidates pending checks on channel changes [#329], prevents state persistence for stale results [#329], and handles out-of-order same-channel checks [#329].
Out of Scope Changes check ✅ Passed All changes are within scope: modifications to AppUpdateService implementation and tests directly address the stale check race conditions outlined in issue #329, with no unrelated alterations.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/desktop/src/services/AppUpdateService/index.ts`:
- Around line 113-117: In setChannel (AppUpdateService), don't
increment/invalidate checkRequestVersion until after the channel write succeeds:
call await this.settings.updateAppUpdateChannel(channel) and await
this.settings.updateAppUpdateLastCheckedAt(null) first, and only then increment
this.checkRequestVersion so in-flight checks aren't incorrectly marked stale if
the persistence fails; adjust the order in the setChannel method accordingly
(references: setChannel, this.settings.updateAppUpdateChannel,
this.settings.updateAppUpdateLastCheckedAt, this.checkRequestVersion).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 3d72d99e-8170-4fea-b4a4-31163ff6b6fe

📥 Commits

Reviewing files that changed from the base of the PR and between f0bd14d and 54f738e.

📒 Files selected for processing (2)
  • apps/desktop/src/services/AppUpdateService/index.ts
  • apps/desktop/tests/services/AppUpdateService/service.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Rust Checks
  • GitHub Check: Frontend Quality
  • GitHub Check: Frontend Tests
  • GitHub Check: Desktop E2E Smoke (Windows)
  • GitHub Check: CodeQL (javascript-typescript)
  • GitHub Check: CodeQL (rust)
🔇 Additional comments (2)
apps/desktop/src/services/AppUpdateService/index.ts (1)

59-60: LGTM!

Also applies to: 130-156, 185-187

apps/desktop/tests/services/AppUpdateService/service.test.ts (1)

173-199: LGTM!

Also applies to: 278-306, 308-349

Comment thread apps/desktop/src/services/AppUpdateService/index.ts
async setChannel(channel: AppUpdateChannel): Promise<void> {
await this.initialize();
await this.settings.updateAppUpdateChannel(channel);
this.checkRequestVersion += 1;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential issue: checkRequestVersion is advanced before updateAppUpdateLastCheckedAt(null) succeeds. If the channel write succeeds but clearing lastCheckedAt fails while a check is in flight, the old check is now treated as stale, while channel-updated is never committed. That can leave the controller stuck in the previous channel's checking state even though the persisted channel has already changed.

Could we cover the updateAppUpdateLastCheckedAt(null) rejection case and either move invalidation until the channel switch is fully committed, or explicitly handle that partial-failure path by committing/rolling back state so the in-flight check does not get stranded?

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.

Updater check results can overwrite newer channel state

2 participants