Skip to content

fix(desktop): fix auto-updater unable to quit the app#3253

Merged
Kitenite merged 5 commits into
mainfrom
fix-autoupdate-quit
Apr 8, 2026
Merged

fix(desktop): fix auto-updater unable to quit the app#3253
Kitenite merged 5 commits into
mainfrom
fix-autoupdate-quit

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Apr 8, 2026

Summary

  • Wrap before-quit host-service cleanup in try/catch so app.exit(0) always runs
  • Replace prepareQuit("release") with exitImmediately() in the auto-updater — bypasses the quit protocol entirely (pre-Host service #3157 approach)
  • Update stale plan doc to reflect actual shipped state

Root Cause

Two regressions from #3157 (Host Service):

  1. Unguarded cleanup before app.exit(0)getHostServiceManager(), releaseAll(), and disposeTray() were added before app.exit(0) without try/catch. If any threw, app.exit(0) was never reached. Combined with the macOS window close handler (also added in Host service #3157) that calls event.preventDefault() + window.hide(), the quit was blocked entirely. Pre-Host service #3157, the window close handler didn't prevent close, so cleanup failures didn't matter.

  2. Updater coupled to quit protocolinstallUpdate() used prepareQuit("release") to set a one-shot quit mode, but the updater doesn't need to participate in the quit lifecycle. Pre-Host service #3157 it used a simple setSkipQuitConfirmation() flag. Now it just calls exitImmediately() after quitAndInstall(), bypassing before-quit entirely.

#3205 fixed part of the problem (removed the background-to-tray block) but didn't address these two issues.

Test plan

  • Build packaged app, trigger auto-update install — app should quit and update
  • Cmd+Q with host services running — app should quit cleanly
  • Cmd+Q with confirm-on-quit enabled — dialog should appear, both Quit and Cancel work
  • Tray "Quit & Stop Services" — services should stop, app exits

Summary by cubic

Fixes the desktop auto-updater failing to quit by simplifying and hardening app shutdown. Quits now always succeed (Cmd+Q and tray), services are released, and updates install reliably.

  • Bug Fixes
    • Wrap before-quit cleanup in try/catch so app.exit(0) always runs; always call releaseAll() and log cleanup errors.
    • Decouple updater from quit lifecycle: call quitAndInstall() then exitImmediately() (drop prepareQuit("release")).
    • Remove QuitMode and pending state; add quitApp() to skip confirmation; simplify tray to a single "Quit Superset"; update the macOS quit/tray plan doc.
    • Sanitize bundle display workspace name to alphanumeric/spaces/hyphens to prevent PlistBuddy errors in dev builds.

Written for commit 324fc60. Summary will update on new commits.

Summary by CodeRabbit

  • Bug Fixes

    • Shutdown hardening: cleanup errors no longer block quitting; app now reliably exits.
    • Update installer reliably terminates the process after starting installation.
    • Workspace identity generation now strips unsafe characters for more robust handling.
  • New Features

    • Simplified tray: a single, consistent "Quit" action always quits the app.
    • Quit confirmation behavior streamlined to avoid inconsistent dialog prompts.
  • Chores

    • Updated macOS lifecycle documentation to reflect refined quit and update flows.

Two regressions introduced in #3157 (Host Service):

1. Host-service cleanup in before-quit had no error handling. If
   getHostServiceManager(), releaseAll(), or disposeTray() threw,
   app.exit(0) was never reached. Combined with the macOS window
   close handler (also added in #3157) that calls event.preventDefault()
   + window.hide(), this blocked the quit entirely.

2. installUpdate() used prepareQuit("release") to participate in the
   quit protocol, but the updater doesn't need to — it can exit
   directly after quitAndInstall().

Fixes:
- Wrap before-quit cleanup in try/catch so app.exit(0) always runs
- Replace prepareQuit("release") with exitImmediately() in the
  auto-updater, reverting to the pre-#3157 approach where the updater
  bypasses the quit protocol entirely
- Update stale plan doc to reflect actual shipped state
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 8, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b972a5d1-33f8-4925-bf1f-7d705ebb9c8c

📥 Commits

Reviewing files that changed from the base of the PR and between 13d44c5 and 324fc60.

📒 Files selected for processing (1)
  • apps/desktop/scripts/patch-dev-protocol.ts

📝 Walkthrough

Walkthrough

Reworks macOS quit/tray lifecycle: removes quit-mode API in favor of a single exported quitApp(); tray always calls quitApp(); before-quit always calls releaseAll() and wraps cleanup + disposeTray() in try/catch to guarantee app.exit(0); updater now calls quitAndInstall() then exitImmediately().

Changes

Cohort / File(s) Summary
Lifecycle Documentation
apps/desktop/plans/20260405-quit-tray-lifecycle.md
Docs updated to reflect new control flow: single quitApp() path from tray; updater uses quitAndInstall() + exitImmediately(); removed prior background-to-tray before-quit block and added requirement to wrap before-quit host-service cleanup in try/catch.
Main quit handler & API
apps/desktop/src/main/index.ts
Removed QuitMode, requestQuit, prepareQuit exports; added quitApp() which sets skipQuitConfirmation and calls app.quit(); before-quit now bases confirmation on skipQuitConfirmation, always calls getHostServiceManager().releaseAll(), and wraps cleanup + disposeTray() in try/catch so app.exit(0) runs.
Tray menu behavior
apps/desktop/src/main/lib/tray/index.ts
Replaced conditional Quit variants and requestQuit calls with a single static “Quit Superset” menu item that always calls quitApp(), removing orgId-based conditional quitting.
Auto-updater install flow
apps/desktop/src/main/lib/auto-updater.ts
Replaced prepareQuit("release") with direct autoUpdater.quitAndInstall(false, true) followed by exitImmediately() to handle macOS/Squirrel timing where quitAndInstall may not immediately quit.
Dev protocol script
apps/desktop/scripts/patch-dev-protocol.ts
bundleDisplayWorkspaceName sanitization tightened: non-ASCII-letter/digit/space/hyphen characters removed via /[^a-zA-Z0-9 -]/g (instead of only replacing /).

Sequence Diagram(s)

sequenceDiagram
    participant Tray as Tray Menu
    participant Main as Main Process
    participant Host as HostServiceManager
    participant OS as macOS / Squirrel

    Tray->>Main: user clicks "Quit Superset" -> quitApp()
    Main->>Main: set skipQuitConfirmation, call app.quit()
    Main->>Main: before-quit handler runs
    Main->>Host: releaseAll()  -- wrapped in try/catch -->
    Main->>Main: disposeTray()  -- wrapped in try/catch -->
    Main->>OS: app.exit(0)
Loading
sequenceDiagram
    participant Updater as AutoUpdater
    participant Main as Main Process
    participant OS as macOS / Squirrel

    Updater->>Main: user selects "Install Update"
    Main->>Updater: autoUpdater.quitAndInstall(false, true)
    Updater->>Main: (may not quit on macOS immediately)
    Main->>Main: exitImmediately()
    Main->>OS: process exits to allow installer to run
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰
I nibble, I hop, I call "quit" with a thump,
One button, one path — no modes to stump,
Try/catch keeps the tidy cleanup bound,
Installer hops in while the process dashes down,
A rabbit cheers: neat exits all around.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix(desktop): fix auto-updater unable to quit the app' clearly and concisely describes the main bug fix: addressing the auto-updater's failure to properly quit the application.
Description check ✅ Passed The PR description comprehensively covers the root causes, changes, test plan, and includes detailed context on related regressions and fixes. All template sections are addressed or appropriately filled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-autoupdate-quit

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
apps/desktop/src/main/lib/auto-updater.ts (1)

5-5: Circular import between index.ts and auto-updater.ts.

This import creates a circular dependency: index.tsauto-updater.tsindex.ts. It's safe at runtime because exitImmediately is only accessed when installUpdate() is called (after both modules are initialized), but circular dependencies can make the codebase harder to reason about.

Consider extracting exitImmediately, requestQuit, and prepareQuit into a dedicated module (e.g., lib/quit.ts) that both files can import from.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/desktop/src/main/lib/auto-updater.ts` at line 5, There is a circular
import caused by importing exitImmediately from index.ts into auto-updater.ts;
extract the shared quit-related functions into a new module (e.g., lib/quit.ts)
that exports exitImmediately, requestQuit, and prepareQuit, then update both
index.ts and auto-updater.ts to import those symbols from lib/quit.ts (leave
installUpdate in auto-updater.ts untouched but call the relocated
exitImmediately from the new module). Ensure the new quit module has the same
API and update any imports/usages in index.ts and auto-updater.ts to avoid the
circular dependency.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/desktop/src/main/lib/auto-updater.ts`:
- Line 5: There is a circular import caused by importing exitImmediately from
index.ts into auto-updater.ts; extract the shared quit-related functions into a
new module (e.g., lib/quit.ts) that exports exitImmediately, requestQuit, and
prepareQuit, then update both index.ts and auto-updater.ts to import those
symbols from lib/quit.ts (leave installUpdate in auto-updater.ts untouched but
call the relocated exitImmediately from the new module). Ensure the new quit
module has the same API and update any imports/usages in index.ts and
auto-updater.ts to avoid the circular dependency.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ebac53c2-e418-48aa-aa40-b0934a398d36

📥 Commits

Reviewing files that changed from the base of the PR and between ed7fb56 and 75f85b9.

📒 Files selected for processing (3)
  • apps/desktop/plans/20260405-quit-tray-lifecycle.md
  • apps/desktop/src/main/index.ts
  • apps/desktop/src/main/lib/auto-updater.ts

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 3 files

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 8, 2026

Greptile encountered an error while reviewing this PR. Please reach out to support@greptile.com for assistance.

Kitenite added 4 commits April 8, 2026 09:45
The QuitMode system ("release" | "stop") was introduced in #3157 and
added unnecessary complexity to the quit lifecycle. It coupled the
auto-updater, tray, and before-quit handler through a shared mutable
state (pendingQuitMode) that was consumed on each before-quit call.

Revert to the simpler pre-#3157 approach:
- Replace QuitMode/requestQuit/prepareQuit with a simple
  skipQuitConfirmation flag and quitApp() helper
- Always releaseAll() on quit (services survive as detached processes)
- Tray menu simplified to single "Quit Superset" item
- before-quit cleanup wrapped in try/catch so app.exit(0) always runs
- Auto-updater uses exitImmediately() — bypasses quit protocol entirely
Workspace names with special characters (e.g. apostrophes) break
PlistBuddy commands. Use an allowlist (alphanumeric, spaces, hyphens)
instead of stripping individual characters.
@Kitenite Kitenite merged commit 146c86d into main Apr 8, 2026
7 checks passed
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 8, 2026

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ⚠️ Neon database branch
  • ⚠️ Electric Fly.io app

Thank you for your contribution! 🎉

MocA-Love added a commit to MocA-Love/superset that referenced this pull request Apr 10, 2026
…m upstream superset-sh#3253)

Cherry-picked from upstream 146c86d (only the patch-dev-protocol.ts
sanitize hunk). The rest of superset-sh#3253 is a quit lifecycle rewrite that
conflicts with fork's requestQuit(mode) system and is intentionally
NOT being taken.

Workspace names with special characters (e.g. apostrophes) break
PlistBuddy commands. Use an allowlist (alphanumeric, spaces, hyphens)
instead of stripping individual characters.
@Kitenite Kitenite deleted the fix-autoupdate-quit branch April 13, 2026 16:35
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