Skip to content

fix(update): auto-restart after Windows binary swap - #6

Merged
706412584 merged 1 commit into
mainfrom
fix/windows-update-auto-restart
Jul 26, 2026
Merged

fix(update): auto-restart after Windows binary swap#6
706412584 merged 1 commit into
mainfrom
fix/windows-update-auto-restart

Conversation

@706412584

@706412584 706412584 commented Jul 26, 2026

Copy link
Copy Markdown
Owner

Summary

  • Update/rollback success now schedules sysutil.RestartServiceAsync() so the newly swapped binary is loaded without relying on a second manual restart click.
  • Windows helper prefers native-control.ps1 restart-app (app-only), falls back to start, and force-kills a stuck old PID before relaunch.

Why

0.1.167 → 0.1.168 already wrote the new exe, but the running process was still the old image; /system/restart ran old code and no-op'd (Service restart via exit only works on Linux...). This removes the chicken-and-egg for future updates once this build is installed.

Test plan

  • go test -tags=unit ./internal/pkg/sysutil/
  • go test -tags=unit ./internal/handler/admin/ -run SystemHandler
  • After merge/release: install this version once, then online-update again and confirm PID/start time changes without manual native-control stop/start

Summary by CodeRabbit

  • New Features

    • System updates and rollbacks now automatically initiate a service restart after completing.
    • Status messages clearly indicate when the restart has been initiated.
  • Bug Fixes

    • Improved Windows restart handling by preferring native restart controls and strengthening process cleanup.
    • Added fallback behavior when the preferred restart method is unavailable.

Schedule platform restart immediately after successful update/rollback so Windows does not stay on the old in-memory process. Prefer native-control restart-app and force-kill stuck PIDs before relaunch.
@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

Service restart behavior

Layer / File(s) Summary
Windows restart script control and teardown
backend/internal/pkg/sysutil/restart_windows.go, backend/internal/pkg/sysutil/restart_windows_test.go
Windows restart scripts now prefer restart-app, fall back to start, and force-stop lingering processes before relaunch.
Delayed restart initiation in admin handlers
backend/internal/handler/admin/system_handler.go, backend/internal/handler/admin/system_handler_test.go
Update and rollback handlers schedule delayed asynchronous restarts and return restart-initiated messages; rollback expectations were updated.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AdminClient
  participant SystemHandler
  participant sysutil.RestartServiceAsync
  participant native-control.ps1
  AdminClient->>SystemHandler: perform update or rollback
  SystemHandler-->>AdminClient: return restart-initiated response
  SystemHandler->>sysutil.RestartServiceAsync: invoke after delay
  sysutil.RestartServiceAsync->>native-control.ps1: restart service
  native-control.ps1-->>sysutil.RestartServiceAsync: restart result
Loading

Possibly related PRs

  • 706412584/sub2api#5: Modifies the same Windows restart script implementation and related tests.

Suggested reviewers: wei-shaw

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: automatic restart behavior after a Windows update/rollback binary swap.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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/windows-update-auto-restart

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

🤖 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 `@backend/internal/handler/admin/system_handler.go`:
- Around line 123-131: Keep system-operation exclusion active until the
scheduled restart handoff begins: update the release/lock flow around the update
restart callback so the 500ms delay does not allow another operation to acquire
the lock, and apply the same restart-pending handling in the rollback restart
callback. Modify both affected sites in
backend/internal/handler/admin/system_handler.go (lines 123-131 and 202-206),
preserving normal release behavior for operations that do not trigger a restart.

In `@backend/internal/pkg/sysutil/restart_windows.go`:
- Around line 81-85: Update the relaunch command construction in the Windows
restart flow to wrap native-control restart-app execution in PowerShell
try/catch. Ensure the start fallback runs both when restart-app throws a
terminating error and when it completes with a non-zero $LASTEXITCODE, while
preserving the final failure propagation when the fallback also fails.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 51a75ba8-17dd-4785-aa33-b530c81411f7

📥 Commits

Reviewing files that changed from the base of the PR and between 757413e and 2031447.

📒 Files selected for processing (4)
  • backend/internal/handler/admin/system_handler.go
  • backend/internal/handler/admin/system_handler_test.go
  • backend/internal/pkg/sysutil/restart_windows.go
  • backend/internal/pkg/sysutil/restart_windows_test.go

Comment on lines +123 to +131
// Schedule platform restart after the response is sent.
// On Windows the process that just swapped the binary still runs the
// old image; waiting for a second /restart click can leave the old
// process alive forever (pre-fix builds no-op, or the user never
// clicks). Linux relies on systemd Restart=always after exit.
go func() {
time.Sleep(500 * time.Millisecond)
sysutil.RestartServiceAsync()
}()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Keep system-operation exclusion active through the restart handoff.

The deferred release(..., succeeded=true) runs as each callback returns, while the restart waits another 500ms. A second update or rollback can therefore acquire the lock and begin before the first operation’s scheduled restart terminates it, risking an interrupted binary operation. Represent “restart pending” in the lock/status flow, or otherwise keep competing system operations excluded until handoff.

  • backend/internal/handler/admin/system_handler.go#L123-L131: prevent a new system operation from starting before the update-triggered restart begins.
  • backend/internal/handler/admin/system_handler.go#L202-L206: apply the same restart-pending exclusion to rollback.
📍 Affects 1 file
  • backend/internal/handler/admin/system_handler.go#L123-L131 (this comment)
  • backend/internal/handler/admin/system_handler.go#L202-L206
🤖 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 `@backend/internal/handler/admin/system_handler.go` around lines 123 - 131,
Keep system-operation exclusion active until the scheduled restart handoff
begins: update the release/lock flow around the update restart callback so the
500ms delay does not allow another operation to acquire the lock, and apply the
same restart-pending handling in the rollback restart callback. Modify both
affected sites in backend/internal/handler/admin/system_handler.go (lines
123-131 and 202-206), preserving normal release behavior for operations that do
not trigger a restart.

Comment on lines 81 to 85
relaunch = fmt.Sprintf(
`& %s start; if ($LASTEXITCODE -ne 0) { throw "native-control start failed: $LASTEXITCODE" }`,
`& %s restart-app; if ($LASTEXITCODE -ne 0) { & %s start }; `+
`if ($LASTEXITCODE -ne 0) { throw "native-control restart/start failed: $LASTEXITCODE" }`,
psQuote(nativeControl),
psQuote(nativeControl),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect native-control action dispatch and its failure signaling; do not execute it.
fd -a '^native-control\.ps1$' . -0 |
  xargs -0 -r rg -n -C3 'restart-app|Unknown|throw|exit|ErrorActionPreference'

Repository: 706412584/sub2api

Length of output: 155


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg '(^|/)native-control\.ps1$|restart_windows\.go$|restart.*windows' || true

echo "== locate native-control =="
fd -a 'native-control|restart_windows' . -0 | sed 's#^\./##' | tr '\0' '\n'

echo "== outline restart_windows if present =="
f="$(fd 'restart_windows\.go' . | head -n 1)"
if [ -n "$f" ]; then
  echo "FILE $f"
  wc -l "$f"
  ast-grep outline "$f" || true
  sed -n '60,130p' "$f"
fi

echo "== search restart-app/native-control references =="
rg -n -C3 'restart-app|native-control|Restart-App|restartApp|Start-App|start-app|Start-Service|ErrorActionPreference' . --glob '!**/.git/**' || true

Repository: 706412584/sub2api

Length of output: 8223


🌐 Web query:

PowerShell $ErrorActionPreference Stop command exit code LASTEXITCODE throw behavior

💡 Result:

In PowerShell, $ErrorActionPreference = 'Stop' controls how non-terminating errors are handled, but it does not directly monitor or react to the $LASTEXITCODE of external commands [1][2]. Mechanism and Behavior: 1. $ErrorActionPreference = 'Stop' escalates non-terminating errors (cmdlet-based) into terminating errors [1][3][4]. 2. This preference does not affect external program exit codes by default [2][5]. Even if set to 'Stop', a failing external command will simply set $LASTEXITCODE to a non-zero value without throwing an exception or aborting the script [2]. 3. To trigger an error in response to a non-zero $LASTEXITCODE, you must manually check the variable and handle it, typically by using 'throw' [2][5]. 4. An exception to this is in PowerShell 7.4+, where you can set $PSNativeCommandUseErrorActionPreference = $true [2]. When enabled, non-zero exit codes from external programs generate a PowerShell error, which is then governed by your $ErrorActionPreference [2]. If $ErrorActionPreference is set to 'Stop' in this scenario, the script will abort upon an external command failure [2]. Throw Behavior: - The 'throw' keyword generates a script-terminating error by default, unwinding the call stack unless caught by a try/catch or trap statement [6]. - $ErrorActionPreference can suppress 'throw' if set to 'SilentlyContinue' or 'Ignore', though the error record is still captured in the $Error variable [6][7]. - If you encounter an external command failure, manually calling 'throw' is the standard way to bridge the gap between $LASTEXITCODE and the PowerShell try/catch error handling mechanism [2][5]. Summary of Interaction: - $ErrorActionPreference = 'Stop' applies to native PowerShell cmdlet errors [4][5]. - To handle external exit codes, check $LASTEXITCODE manually or use $PSNativeCommandUseErrorActionPreference = $true in PowerShell 7.4+ to integrate them into the standard error stream [2].

Citations:


🌐 Web query:

PowerShell 7.4 PSNativeCommandUseErrorActionPreference true error code exceptions

💡 Result:

In PowerShell 7.4, the preference variable $PSNativeCommandUseErrorActionPreference allows you to manage how native commands with non-zero exit codes are handled [1][2]. When set to $true, a non-zero exit code triggers the creation of a NativeCommandExitException, which is treated as a PowerShell error [3][4]. Key behaviors include: 1. Error Type: When $PSNativeCommandUseErrorActionPreference is $true, a non-zero exit code results in a non-terminating error by default [3][5]. 2. ErrorActionPreference Integration: This error respects the current $ErrorActionPreference [3][2]. If you set $ErrorActionPreference to Stop, the non-terminating error is promoted to a script-terminating error [3][5]. This allows you to catch the exception using standard try/catch blocks [3][6]. 3. Exception Details: The error is specifically represented by a NativeCommandExitException [3][4]. This exception contains details such as the path of the native command, the non-zero exit code returned, and the process ID [4]. 4. Scope Control: Because it is a preference variable, you can localize its effect by setting it within a script block [1][2]. This is particularly useful for native commands like robocopy that may return non-zero exit codes for informational purposes rather than errors [1]. Prior to PowerShell 7.4, this functionality was available as an experimental feature (PSNativeCommandErrorActionPreference) [7][6]. In 7.4, it became a mainstream, stable feature [7][8]. Example of handling native command errors with try/catch: $ErrorActionPreference = 'Stop' $PSNativeCommandUseErrorActionPreference = $true try { # Replace with a command that returns a non-zero exit code & "my-native-command.exe" } catch [System.Management.Automation.NativeCommandExitException] { Write-Host "Caught native command error: $($_.Exception.Message)" }

Citations:


🌐 Web query:

PowerShell $ErrorActionPreference Stop cmdlet error LastExitCode undefined command behavior

💡 Result:

In PowerShell, the behavior of an undefined command (a command not found by the shell) is determined by the shell's parsing process before it ever interacts with the $ErrorActionPreference variable or generates an exit code [1][2]. When you attempt to run an undefined command: 1. Command Parsing: PowerShell's engine attempts to resolve the command name. If it cannot find the command as a cmdlet, function, script, or executable, it throws a non-terminating error (or a statement-terminating error depending on context/host) indicating the command is not recognized. Because this occurs at the parsing or binding stage, $ErrorActionPreference = 'Stop' generally does not intercept or handle "command not found" errors in the way it handles errors emitted by executing cmdlets [1][2]. 2. $LASTEXITCODE Behavior: $LASTEXITCODE is specifically designed to store the exit code of the last Win32 executable that was executed [3][4]. If an attempt to run a command fails because the command itself is undefined (and thus no external process was ever successfully launched), $LASTEXITCODE will retain its previous value and will not be updated to reflect a failure [3][5]. It does not indicate success or failure for PowerShell internal operations [4][5]. 3. Success Tracking ($?): The automatic variable $? provides the most reliable way to check the success of the last operation [6][4]. If a command fails to execute because it is undefined, $? will be set to $False [6][4]. To reliably handle undefined commands or external process failures: - Use $? to check if the last operation succeeded [3][4]. - Do not rely on $LASTEXITCODE to detect if a command was found; only use it to check the exit code of an external process that successfully started [3][5]. - If you need to stop script execution when a command is missing, explicitly check $? after the command call and throw an error if it is $False [7]. Top results: [1], [3], [4], [5]

Citations:


🌐 Web query:

PowerShell try catch command not found $ErrorActionPreference Stop statement-terminating error

💡 Result:

In PowerShell, a CommandNotFoundException is classified as a statement-terminating error [1]. By default, statement-terminating errors stop the current statement from executing but do not necessarily terminate the entire script [2]. To ensure a CommandNotFoundException is caught by a try/catch block, you must escalate it to a script-terminating error [3][4]. 1. Using $ErrorActionPreference = 'Stop': When you set $ErrorActionPreference = 'Stop' within a scope, PowerShell elevates both non-terminating and statement-terminating errors to script-terminating errors [3][5]. This causes the error to propagate up the call stack, allowing the try/catch block to intercept it [3][6]. 2. Using -ErrorAction Stop: While the common parameter -ErrorAction Stop is frequently used to handle errors, its effect on command-not-found scenarios can vary depending on the command type [6]. For maximum robustness in ensuring a statement-terminating error is caught, wrapping the command in a try/catch block is the standard approach [4][7]. Example of catching a command not found error: try { # If the command is missing, this will trigger the catch block NonExistentCommand -ErrorAction Stop } catch [System.Management.Automation.CommandNotFoundException] { Write-Host "Caught the CommandNotFoundException: $($.Exception.Message)" } catch { Write-Host "Caught a different error: $($.Exception.Message)" } Key concepts: - Statement-terminating errors: Stop the specific command or statement but allow the script to continue by default [2]. - Script-terminating errors: Stop the execution of the entire script or call stack unless handled by try/catch or trap [2][3]. - $ErrorActionPreference = 'Stop': Promotes errors to script-terminating, ensuring they are catchable [3][5]. - Catching specific exceptions: You can catch specific error types, such as [System.Management.Automation.CommandNotFoundException], to handle them uniquely [7].

Citations:


🌐 Web query:

PowerShell command not found non-zero lastexitcode with $ErrorActionPreference Stop actual exit code

💡 Result:

When a command (external program) is not found in PowerShell, it typically triggers a CommandNotFoundException [1]. Whether this behavior interacts with $ErrorActionPreference and affects the final exit code depends on how you run the command and the PowerShell version [2][3][4]. To ensure robust error handling and proper exit code management, follow these guidelines: 1. Understanding CommandNotFound: When PowerShell fails to find an executable, it throws a non-terminating error by default [1]. If $ErrorActionPreference is set to 'Stop', this error is promoted to a script-terminating error [5][6]. 2. Using $PSNativeCommandUseErrorActionPreference (PowerShell 7.4+): For external programs, $ErrorActionPreference alone does not natively control exit code behavior in older versions [3][4]. In PowerShell 7.4 and later, you can enable the $PSNativeCommandUseErrorActionPreference feature [2][4]: - Set $PSNativeCommandUseErrorActionPreference = $true. - Set $ErrorActionPreference = 'Stop'. When enabled, any non-zero exit code from an external program generates a NativeCommandExitException [2][7]. Because this is a PowerShell error, it respects $ErrorActionPreference, effectively allowing you to catch the failure in a try/catch block [2][8][4]. 3. Manual Exit Code Handling (Recommended for Compatibility): If you cannot rely on the $PSNativeCommandUseErrorActionPreference feature (e.g., in Windows PowerShell or for broader script compatibility), you must manually check the exit code [3][4]: try { # Attempt to run the command $commandOutput = my-command if ($LASTEXITCODE -ne 0) { throw "Command failed with exit code $LASTEXITCODE" } } catch { # Handle the error, including 'command not found' or non-zero exit codes Write-Error "An error occurred: $($_.Exception.Message)" exit 1 } 4. Important Caveats: - -ErrorAction Stop: Do not pass -ErrorAction Stop to external programs; it is a common parameter for PowerShell cmdlets and will likely be passed as an invalid argument to the external program itself [9]. - $?: Avoid relying solely on $? to detect failure for external programs, as it can be set to $false by stderr output even when the exit code is 0 [10][3]. Always check $LASTEXITCODE [3][4]. - pwsh -Command: Be aware that running a script via pwsh -Command may default the process exit code to 1 if an exception occurs or $? is $false, regardless of the script's intended exit code [11]. Use explicit exit statements if you need to control the shell's final return value [11][4].

Citations:


Wrap native-control relaunches in try/catch before relying on fallback logic.

With $ErrorActionPreference = 'Stop', a terminatable PowerShell failure during restart-app can abort the remaining script before the $LASTEXITCODE checks run. Catch these failures and run the start fallback for both caught errors and non-zero exit codes so the old Windows process exit doesn’t leave the service stopped.

🤖 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 `@backend/internal/pkg/sysutil/restart_windows.go` around lines 81 - 85, Update
the relaunch command construction in the Windows restart flow to wrap
native-control restart-app execution in PowerShell try/catch. Ensure the start
fallback runs both when restart-app throws a terminating error and when it
completes with a non-zero $LASTEXITCODE, while preserving the final failure
propagation when the fallback also fails.

@706412584
706412584 merged commit 886169f into main Jul 26, 2026
15 checks passed
@706412584
706412584 deleted the fix/windows-update-auto-restart branch July 26, 2026 09:55
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