Skip to content

fix(updater): ignore stale check results - #330

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

fix(updater): ignore stale check results#330
ThunderTr77 wants to merge 7 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

Warning

Review limit reached

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

Next review available in: 41 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

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: ASSERTIVE

Plan: Pro Plus

Run ID: 169b0d74-aa7a-40a4-bf0e-11d44465633a

📥 Commits

Reviewing files that changed from the base of the PR and between 8cb8b3e and d414e13.

📒 Files selected for processing (2)
  • apps/desktop/src/services/AppUpdateService/index.ts
  • apps/desktop/tests/services/AppUpdateService/service.test.ts
📝 Walkthrough

Walkthrough

AppUpdateController adds per-request versioning so only the latest check for the active channel can commit completion/failure or persist lastCheckedAt. Tests and test helpers are extended 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 setChannel and per checkNow call, captures a local requestVersion, adds isCurrentCheckRequest predicate, and short-circuits stale success/error paths to prevent stale state/persistence.
Race and persistence test coverage
apps/desktop/tests/services/AppUpdateService/service.test.ts
Extends createController with updateChannelError/updateLastCheckedAtError injection and adds tests: stale automatic/manual checks after channel switch, setChannel persistence failure during in-flight check, failed clearing of channel timestamp, and overlapping manual checks where newer result wins.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • TouchAI-org/TouchAI#315: Related updater work preventing stale download-completed/failed actions from overwriting state after a channel switch.

Poem

🐰 A numbered hop, each check wears a crest,
Old results get gated, the newest is blessed.
Channels leap sideways, races conclude—
Only the freshest timestamp gets renewed.
Tiny paws persist the tidy truth.

🚥 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 The PR title 'fix(updater): ignore stale check results' follows Conventional Commits format and accurately summarizes the main change.
Description check ✅ Passed The PR description is comprehensive with all required sections: summary, linked issue, AI disclosure, testing evidence, risk notes, and completed checklist.
Linked Issues check ✅ Passed The changes fully address all objectives from issue #329: request versioning prevents stale checks, channel changes invalidate pending results, and out-of-order checks are handled correctly.
Out of Scope Changes check ✅ Passed All code changes are directly within scope, modifying only AppUpdateController logic and tests to add request versioning and stale result guards.

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

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

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.

@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 Outdated
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 7, 2026
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?

@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.

♻️ Duplicate comments (1)
apps/desktop/src/services/AppUpdateService/index.ts (1)

115-119: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Handle setChannel partial persistence failure as an atomic state transition.

If updateAppUpdateChannel(channel) succeeds but updateAppUpdateLastCheckedAt(null) fails, setChannel rejects before channel-updated commit and before version invalidation. That can leave persisted channel/state out of sync and allow an in-flight pre-switch check to still persist a timestamp.

Suggested direction
 async setChannel(channel: AppUpdateChannel): Promise<void> {
     await this.initialize();
     await this.settings.updateAppUpdateChannel(channel);
-    await this.settings.updateAppUpdateLastCheckedAt(null);
-    this.checkRequestVersion += 1;
-    this.commit({ type: 'channel-updated', channel });
+    try {
+        await this.settings.updateAppUpdateLastCheckedAt(null);
+    } catch (error) {
+        // Channel persistence already succeeded; keep controller state aligned.
+        this.checkRequestVersion += 1;
+        this.commit({ type: 'channel-updated', channel });
+        throw error;
+    }
+    this.checkRequestVersion += 1;
+    this.commit({ type: 'channel-updated', channel });
 }
🤖 Prompt for 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.

In `@apps/desktop/src/services/AppUpdateService/index.ts` around lines 115 - 119,
The setChannel flow can leave persisted state inconsistent if
updateAppUpdateChannel(channel) succeeds but updateAppUpdateLastCheckedAt(null)
fails; make the transition atomic by ensuring both settings updates succeed
before mutating in-memory state or committing. In setChannel, read and store the
previous channel, perform both settings.updateAppUpdateChannel(channel) and
settings.updateAppUpdateLastCheckedAt(null) (or perform last-checked update
first), and only after both succeed increment this.checkRequestVersion and call
this.commit({ type: 'channel-updated', channel }); if the second update fails,
revert by calling settings.updateAppUpdateChannel(previousChannel)
(catch/rethrow errors appropriately) so the persisted channel remains
consistent.
🤖 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.

Duplicate comments:
In `@apps/desktop/src/services/AppUpdateService/index.ts`:
- Around line 115-119: The setChannel flow can leave persisted state
inconsistent if updateAppUpdateChannel(channel) succeeds but
updateAppUpdateLastCheckedAt(null) fails; make the transition atomic by ensuring
both settings updates succeed before mutating in-memory state or committing. In
setChannel, read and store the previous channel, perform both
settings.updateAppUpdateChannel(channel) and
settings.updateAppUpdateLastCheckedAt(null) (or perform last-checked update
first), and only after both succeed increment this.checkRequestVersion and call
this.commit({ type: 'channel-updated', channel }); if the second update fails,
revert by calling settings.updateAppUpdateChannel(previousChannel)
(catch/rethrow errors appropriately) so the persisted channel remains
consistent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: feeca101-181d-499a-9241-f5d7d04593b0

📥 Commits

Reviewing files that changed from the base of the PR and between 9ba57af and 8cb8b3e.

📒 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). (4)
  • GitHub Check: Desktop E2E Smoke (Windows)
  • GitHub Check: Frontend Tests
  • GitHub Check: Rust Checks
  • GitHub Check: CodeQL (rust)
🔇 Additional comments (1)
apps/desktop/tests/services/AppUpdateService/service.test.ts (1)

43-53: LGTM!

Also applies to: 75-83

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 11, 2026
@github-actions

github-actions Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

This pull request has been inactive for 21 days. Please update it or leave a note if it is still in progress.

@github-actions github-actions Bot added the Stale label Jul 3, 2026
@hiqiancheng

Copy link
Copy Markdown
Collaborator

setChannel() 持久化期间旧 check 仍可能写入结果,需要先失效旧请求。

Old checks can still commit while setChannel() is persisting; invalidate them before persistence.

@github-actions github-actions Bot removed the Stale label Jul 7, 2026
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