Repository navigation
feat(desktop): guard quits and restarts with host activity checks - #362
Merged
Merged
Conversation
incognitojam
force-pushed
the
styal/host-activity-shutdown
branch
from
September 18, 2026 19:00
925531e to
f24081b
Compare
incognitojam
marked this pull request as ready for review
September 18, 2026 19:00
Owner
Author
|
Manually tested on dev:desktop on macOS: asked an agent to do some work in a new thread, hit Cmd+Q twice and the dialog appeared. Hit cancel, waited for the agent to finish, then Cmd+Q twice again and it did close without the dialog. |
incognitojam
added a commit
that referenced
this pull request
Sep 25, 2026
> [!NOTE] > The in-app **Update server** action now checks the server for running work before restarting it, using the same activity check and dialog wording as desktop quit and restart. Idle servers update without a dialog. The **Update server** action restarted a connected server without checking what was running on it. The only confirmation was a fixed prompt for desktop-hosted servers, so updating a CLI or service-hosted server could interrupt an agent turn, a background task such as a Claude monitor, or a terminal command, including work started from another device. Desktop quit and restart already check this through the server's host activity (#362), but that check was limited to backends the desktop app hosts. ## Behavior - Before updating, the client reads the server's host activity: active and waiting threads (including background work), running terminal commands, and terminals that could not be inspected. - Idle servers update immediately. Otherwise a confirmation lists what will be interrupted, for example "Update the Lab server with running work?" followed by "1 thread will be interrupted." Cancel leaves the server running. - With **Continue threads after restarts** on, threads that continuation will resume are listed as continuing after the restart, and every other active thread as interrupted. That includes a thread whose turn has finished but that still has background work such as a monitor, which continuation does not resume today (upstream `pingdotgg#13400`, tracked in #454). - If the check fails or takes longer than five seconds, including on servers that predate this change, the dialog says activity could not be checked and still lets the user continue. - Desktop-hosted servers keep their unconditional relaunch confirmation, which now also lists running work. ## Implementation - `listContinuableThreads` in `serverRuntimeStartup.ts` is now the one place that selects threads for continuation. The self-update marking step uses it, and the activity check reports how many active threads it selects as `continuableSessions`. If upstream widens the selection, the dialog changes with it. - `server.getHostActivity` is a new WebSocket RPC (orchestration read scope) that returns the existing `inspectHostActivity` result. It uses the WebSocket rather than the existing `/api/environment/activity` HTTP endpoint so it works over relay and tunnel connections as well as direct ones. - `describeHostActivity` in `@t3tools/shared/hostActivity` turns one or more activity results into the dialog lines and the kind of confirmation needed. The desktop shutdown guard and the web update action both use it, so their wording stays in sync, and a multi-server update action can pass several results in and get combined counts. Desktop quit and restart output is unchanged. - User docs, the styal differences overview, and the fork feature ledger describe the new check. ## Screenshots The same thread, with a Codex turn and a terminal command both running. Before this change, **Update** restarted the server immediately. It now asks first. | Before: Update restarts the server immediately | After: confirmation lists the running work | | --- | --- | |  |  | With **Continue threads after restarts** on, a running turn that continuation will resume is listed as continuing:  ## Validation Real client: a web dev server on isolated synthetic state, with the client forced to see the server as behind and supporting in-app updates. The server update RPCs were stubbed to fail, so confirming did not install anything. None of that is part of this diff. - Idle server, Update from Settings → Connections: no dialog; the update request was sent. - `sleep 600` running in a thread terminal, Update from the chat banner: "Update the server with running work? 1 terminal session will be interrupted." Cancel sent no update request. - Same state, Update from Settings → Connections: the same dialog. Confirm sent the update request. - A real Codex turn running `sleep 120` plus the terminal: "1 thread will be interrupted." and "1 terminal session will be interrupted." - A real Codex turn with **Continue threads after restarts** on: "1 thread will continue after the restart." With the setting off, the same turn: "1 thread will be interrupted." - Automated tests: - Server: host activity tests count continuable threads only among active threads, and the existing continuation-marking tests pass against the shared selection. - Server: a WebSocket test calls `server.getHostActivity` against the app under test with a running provider session and mocked terminal inspection, and receives those counts. - Web: component tests cover idle servers updating without a dialog, Cancel leaving the server alone, Confirm starting the update, and a failed activity check still asking. Logic tests cover the dialog text for server and desktop-app updates. - Shared: tests cover adding up counts across hosts, waiting threads, continuing versus interrupted threads, uninspected terminals, and failed or missing host checks. - Desktop: the existing shutdown-guard suite passes unchanged against the shared helper. - Typecheck passes for contracts, shared, client-runtime, server, desktop, and web. Not exercised in a real client: a thread waiting for approval, a thread with only background work, a remote desktop-app update, and a real install after Confirm. These are covered by the automated tests above, except the real install, which this change does not touch. --- Written by an agent (Claude Code, claude-opus-5-5).
incognitojam
pushed a commit
that referenced
this pull request
Oct 2, 2026
(cherry picked from commit 3be02ae) Fork adaptation: The web root keeps the fork's single ConfirmDialogHost, which the fork mounts in AppRoot so desktop quit and restart checks can use it before the router (fork #362), and adds only the snooze dialog host. The thread sidebar guide keeps the fork's notification section before the new snooze section. The lockfile is regenerated from the fork's own, so react-day-picker resolves to 10.0.2 rather than upstream's 10.0.1. The two daylight saving time tests run only where a stubbed TZ takes effect, because Fork CI runs workspace tests with --pool=threads and a worker thread keeps the process time zone. Upstream-PR: 11800 Fork-Feature: desktop-background-updates
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
Desktop quits and restarts check activity across their owned environments. Idle hosts proceed immediately; running work or failed checks use the existing in-app confirmation dialog. Settings and sidebar update actions were exercised in a packaged macOS app with a controlled installer handoff.
“Restart and install” previously asked for confirmation every time, while ordinary quits could interrupt hosted work without checking it. The desktop now checks activity first: idle hosts restart immediately, and work that would be interrupted is described in the existing styled dialog.
The check covers every owned backend, including WSL and work started from other devices. It combines provider sessions, thread projections, and fresh terminal process inspection with a three-second budget per backend. Active turns, approval/input waits, background work, running commands, and failed checks require confirmation; idle shells do not. Failed terminal inspections are counted separately, so “could not be checked” appears only when inspection actually fails.
One shared guard handles Quit, relaunch, and explicit update installation. Update installation keeps the title
Install update <version> and restart styal?and uses counts such as “1 terminal session will be interrupted. Make sure you're ready before continuing.” Cancel preserves running work and downloaded updates and restores preferences when a network or WSL settings restart is declined. Disabling WSL-only uses one settings call, preventing cancellation from triggering a second restart request.The confirmation host remains mounted on authentication and recovery screens. If macOS has no open window, a required confirmation reopens it. The renderer acknowledges only when the dialog becomes active, not while queued behind another confirmation. If it cannot present the dialog within five seconds, the request expires and a subsequent explicit Quit can bypass confirmation while still running normal shutdown and cleanup. Update installation and settings restarts cannot consume that override. A displayed dialog or renderer recovery clears it, and expired requests cannot authorize shutdown through late acknowledgments or responses. Displayed dialogs have no response deadline. Windows/Linux window closes enter the guard before destroying the window; closing the last macOS window continues to leave the host running.
Desktop bootstrap credentials remain available for the owning server's lifetime, including first use after days of uptime and renewal after bearer expiry. Pairing links and bearer sessions retain their normal expiry. This is a point-in-time check for manual shutdown; automatic restart remains disabled. Independent remote servers, service updates, process termination signals, and updater-controlled quits retain their existing behavior.
In-app confirmation
The existing unconditional update prompt is shown on the left. The right shows the activity-aware prompt with a running terminal command. An idle host skips the dialog.
Validation
The macOS update-button checks used the real renderer, IPC, activity endpoint, and backend shutdown. Update readiness was supplied through updater events, and the OS installer handoff was replaced with a recorder and app relaunch. The update checks produced no application errors; the recovery checks deliberately injected authentication and renderer failures. Test apps, backends, and the local update feed were stopped.
Verification limits
Written by an agent (Codex, GPT-6).