fix: harden restart_claude_desktop across platforms (#33)
CI / Lint (ruff) (pull_request) Successful in 7s
CI / Tests (py3.10) (pull_request) Successful in 9s
CI / Tests (py3.12) (pull_request) Successful in 10s

- Linux (and any non-desktop platform): refuse instead of 'pkill claude',
  which substring-matched running Claude Code CLI sessions and relaunched
  the CLI, not a desktop app. New restart_supported() gates the button.
- macOS: wait (<=5s) for the old instance to exit before 'open -a Claude'
  so the relaunch can't re-activate the dying process. Runs off the UI
  thread via a RestartWorker.
- Windows: verify the Start-menu shortcut exists BEFORE taskkill, so an
  MSIX/Store install is never killed without a relaunch path.

Closes #33

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cowork Supervisor
2026-07-12 12:58:15 -04:00
parent 06326e5e9d
commit c493aa0c84
3 changed files with 152 additions and 65 deletions
+29 -6
View File
@@ -1423,6 +1423,17 @@ class AboutDialog(QDialog):
QDesktopServices.openUrl(QUrl(self._release_url or core.RELEASES_URL))
# --------------------------------------------------------------------------- #
# Restart worker: core.restart_claude_desktop() blocks up to ~5 s on macOS
# waiting for the old instance to exit, so it must run off the UI thread.
# --------------------------------------------------------------------------- #
class RestartWorker(QThread):
done = Signal(object) # core.RestartResult
def run(self):
self.done.emit(core.restart_claude_desktop())
# --------------------------------------------------------------------------- #
# Main window
# --------------------------------------------------------------------------- #
@@ -2284,18 +2295,30 @@ class MainWindow(QMainWindow):
def _offer_restart_button(self):
"""Show the 'Restart Claude Desktop' button after a successful save,
but only when the just-saved profile is Claude Desktop -- restarting
makes no sense for Claude Code, which has no GUI process to bounce."""
if self.current_profile and core.profile_targets_claude_desktop(self.current_profile):
makes no sense for Claude Code, which has no GUI process to bounce --
and only on platforms where Claude Desktop exists (never Linux, where
'claude' is the Claude Code CLI)."""
if (
self.current_profile
and core.profile_targets_claude_desktop(self.current_profile)
and core.restart_supported()
):
self.restart_btn.show()
else:
self.restart_btn.hide()
def _restart_claude_desktop(self):
self.restart_btn.setEnabled(False)
try:
result = core.restart_claude_desktop()
finally:
self.restart_btn.setEnabled(True)
self.restart_btn.setText("Restarting…")
# Held on self (MainWindow outlives the worker); replaced only after
# done re-enables the button, so a running thread is never dropped.
self._restart_worker = RestartWorker()
self._restart_worker.done.connect(self._on_restart_done)
self._restart_worker.start()
def _on_restart_done(self, result):
self.restart_btn.setEnabled(True)
self.restart_btn.setText("Restart Claude Desktop")
self.restart_btn.hide()
if result.success:
self.status.setText(f"{self.status.text()} · {result.detail}")