Skip to content

fix(apollo): run seat Apollo without a console so Restart and Quit are visible to the service (#124) - #128

Merged
vibesoftwarecoder merged 2 commits into
masterfrom
fix/seat-apollo-supervision
Oct 21, 2026
Merged

vibesoftwarecoder merged 2 commits into
masterfrom
fix/seat-apollo-supervision

Conversation

@vibesoftwarecoder

Copy link
Copy Markdown
Owner

Fixes #124.

What was wrong. Seat Apollo was started with a new console. With a console, Apollo's Restart starts a copy of itself and then exits. The service only saw its tracked process exit, so it started a second Apollo. Both then ran on the seat's ports. Quit also exited with code 0, so the service started Apollo again within a few seconds.

What this changes.

  • Seat Apollo now starts with no console window (CREATE_NO_WINDOW). The service keeps the process handle so it can read the exit code. With no console, Apollo acts as it does under its own Windows service. Restart exits with 0 and starts nothing. Quit exits with 1115.
  • The health check reads the exit code. After a Quit, Apollo stays stopped. The seat shows a message and the Start button, and no restart attempt is used. After a Restart, the service starts Apollo again without using a restart attempt. Any other exit is treated as a crash, as before.
  • Before every Apollo start and at the end of every stop, the service ends any sunshine.exe that runs this seat's exact config file. Other seats' Apollo and the console Apollo never match.
  • After a Quit, or the dashboard's Stop, the seat's Apollo stays stopped until the user starts it. That holds through a session drop and the reconnect after a sleep. The stop is cleared by Start, Restart, reprovision or teardown. It is kept in memory only, so a service restart or reboot starts seats as usual.

This reverses the #120 choice on purpose. #120 and #121 kept the console so Apollo's Restart and Quit would work as before. That console is the cause of this bug. The tests that asserted the console are rewritten. The minimized-window setting from #121 is kept. It does no harm, and it still helps if the Apollo exe is ever a GUI build.

Tested in the unit tests. They cover the launch flags, the exit-code rules, the health-check decision, the restart limit, the config-path match (including a near-miss seat name and the console Apollo), and the stopped-by-user marker. The new tests were checked by breaking the code on purpose: they failed, then passed again. The full suite passes: 1702 passed, 18 skipped, 0 failed.

Tested live on 2026-10-21, on a seat built from this branch on top of the 0.6.22 release (0.6.22+321ea0fe2):

  • Restart from Apollo's web UI left exactly one Apollo, with the service as its parent, and no restart attempt was used. Before this change the same test left two.
  • Quit left the seat's Apollo stopped for the 70 seconds watched. The service logged exit code 1115, and the seat showed the Start message.
  • Start brought one Apollo back and cleared the message.
  • A game ran in the seat's session and streamed at about 20% video encode. Apollo kept writing its log with no console.
  • Force-killing Apollo made the service relaunch it within a few seconds, and the restart count went from 0 to 1.
  • The console Apollo's process and start time did not change at any step.

Not tested live: a real sleep and wake after a Quit. That case is covered by the unit tests, and the dashboard's Stop uses the same code. The WMI step that ends stray Apollo processes was not exercised against a real stray, only its path matcher is unit-tested.

Changing the NVENC preset, resolution or scale restarts Apollo through the normal start path. On a seat whose Apollo was stopped by the user, that brings Apollo back and clears the stop.

🤖 Generated with Claude Code

vibesoftwarecoder and others added 2 commits October 21, 2026 13:38
…e visible to the service (#124)

Seat Apollo was launched with CREATE_NEW_CONSOLE. With a console,
Apollo's Restart starts a copy of itself and exits 0, and Quit also
exits 0. The service saw only that its tracked PID was gone, launched a
second Apollo, and both ran on the seat's ports. Quit was undone within
one health check.

- Launch seat Apollo with CREATE_NO_WINDOW and keep the process handle
  (LaunchedProcess), so its exit code can be read. Apollo then sees
  GetConsoleWindow() == NULL, as under its own Windows service
  (tools/sunshinesvc.cpp): Restart exits 0 and starts nothing, Quit
  exits 1115 (ERROR_SHUTDOWN_IN_PROGRESS).
- The health check classifies the exit (ApolloExit). 1115 leaves Apollo
  stopped, as the dashboard's Stop does, with a message on the seat and
  no restart attempt spent. 0 relaunches without spending an attempt,
  unless it came within the 30 s startup window. Anything else, or an
  unreadable code, is a crash and is handled as before.
- Before every launch and at the end of every stop, kill any
  sunshine.exe whose command line passes exactly this seat's
  sunshine.conf (ApolloOwnership.CommandLineNamesConfig, WMI, confirmed
  again while a handle pins the PID). Other seats and the console
  Apollo never match. A WMI failure kills nothing.

This reverses the #120/#121 choice to keep CREATE_NEW_CONSOLE, on
purpose. The tests that asserted it are rewritten to assert the
opposite. The SW_SHOWMINNOACTIVE request from #121 is kept: it is
harmless, and it still applies if the exe is ever a GUI-subsystem build,
for which CREATE_NO_WINDOW is ignored. Games, on-connect apps and the
console-session Apollo launch are unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ross a sleep (#124)

After a Quit from Apollo's web UI or tray, or the dashboard's Stop, the
health check's crash relaunch already left Apollo down. Its reconnect
after a session drop did not: it called RestartAsync, which fell
through to StartAsync and brought Apollo back after every sleep.

- ApolloManager.StopByUser stops Apollo and marks the seat as stopped
  by the user. Quit (exit 1115) and SeatManager.StopApollo both use it.
- The reconnect rescue reconnects the session but does not start Apollo
  while the seat is marked (RestartApolloAfterReconnectAsync).
- The mark is cleared by an explicit Start or Restart, by a successful
  StartAsync, and by teardown. A reprovisioned seat is a new seat.
- It is in memory only, like the seats. A service restart or reboot
  provisions auto-start seats and starts Apollo as usual. No API shape
  changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vibesoftwarecoder
vibesoftwarecoder merged commit d23ca79 into master Oct 21, 2026
3 checks passed
@vibesoftwarecoder
vibesoftwarecoder deleted the fix/seat-apollo-supervision branch October 21, 2026 19:48
vibesoftwarecoder added a commit that referenced this pull request Oct 21, 2026
Bump version.txt and add the 0.6.23 notes.

- #128 (fixes #124)
- #127 (fixes #126)

Co-authored-by: vibesoftwarecoder <vibesoftwarecoder@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5.5 <noreply@anthropic.com>
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.

After Restart from a seat's Apollo web UI, two Apollo processes are left running

1 participant