From f4c2dd76e42b1917e7ddcb94e6d3f23679d7cf54 Mon Sep 17 00:00:00 2001 From: Sthreal <2066885216@qq.com> Date: Sat, 26 Sep 2026 17:54:13 +0800 Subject: [PATCH] fix(update): fall back to foreground daemon restart --- CHANGELOG.md | 6 ++++++ README.md | 2 +- crates/bsk-cli/src/cli/update.rs | 24 ++++++++++++++++++------ crates/bsk-cli/tests/windows_update.rs | 12 +++++++++--- 4 files changed, 34 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e03b06d2..04c10529 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/). Starting from 0.2.0, CLI / Extension / DSH Plugin share the same version number. +## [Unreleased] + +### Fixed + +- Windows auto-update now falls back to a supervised foreground daemon when the host Job Object denies process breakaway, preventing an upgrade from leaving the browser pipeline disconnected. + ## [0.3.1] - 2026-09-23 ### Added diff --git a/README.md b/README.md index d156ac3f..3bcc0fe5 100644 --- a/README.md +++ b/README.md @@ -248,7 +248,7 @@ Finish active browser tasks, then update the CLI: bsk update --yes ``` -For the default local setup, this restarts a running daemon when an update is installed. If Windows reports a staged update, wait for replacement to finish. If you use the installer to replace the binary, restart the daemon afterwards with `bsk daemon restart`. +For the default local setup, this restarts a running daemon when an update is installed. On Windows, the update helper first tries a detached daemon restart. If the host Job Object denies breakaway, it falls back to a supervised `bsk daemon start --foreground` process and remains its parent until the daemon exits. Set `BSK_AUTO_UPDATE=off` to disable daemon-side auto-update while keeping manual `bsk update` available. If you use the installer to replace the binary, restart the daemon afterwards with `bsk daemon restart`. Update the extension through its browser store. Update the DSH plugin separately, then restart its profile: diff --git a/crates/bsk-cli/src/cli/update.rs b/crates/bsk-cli/src/cli/update.rs index f219aa75..40311f8e 100644 --- a/crates/bsk-cli/src/cli/update.rs +++ b/crates/bsk-cli/src/cli/update.rs @@ -831,11 +831,18 @@ fn windows_replacement_script(restart_args: Option<&StartArgs>, attempts: u32) - // Only fixed switches and numeric values go into the ASCII script. Paths // stay in Unicode environment variables, including %, ! and shell symbols. let restart = restart_args.map_or_else(String::new, |args| { - format!( - "\"%BSK_UPDATE_TARGET%\" daemon start --port {} --session-idle {}ms --daemon-idle {}ms\r\nif errorlevel 1 goto failed_restart\r\n", + let flags = format!( + "--port {} --session-idle {}ms --daemon-idle {}ms", args.resolved_port(), args.resolved_session_idle().as_millis(), args.resolved_daemon_idle().as_millis(), + ); + format!( + "\"%BSK_UPDATE_TARGET%\" daemon start {flags}\r\n\ + if not errorlevel 1 goto restarted\r\n\ + echo Detached daemon restart failed; retrying in foreground mode.\r\n\ + \"%BSK_UPDATE_TARGET%\" daemon start --foreground {flags}\r\n\ + if errorlevel 1 goto failed_restart\r\n", ) }); format!( @@ -857,6 +864,7 @@ fn windows_replacement_script(restart_args: Option<&StartArgs>, attempts: u32) - goto failed\r\n\ :replaced\r\n\ {restart}\ + :restarted\r\n\ del /F /Q \"%BSK_UPDATE_LOG%\" >nul 2>nul\r\n\ (del /F /Q \"%BSK_UPDATE_SCRIPT%\" >nul 2>nul & exit 0)\r\n\ :failed_restart\r\n\ @@ -1060,11 +1068,15 @@ mod tests { ..Default::default() }; let script = windows_replacement_script(Some(&args), 120); + let detached = "daemon start --port 54321 --session-idle 1234ms --daemon-idle 75000ms"; + let foreground = + "daemon start --foreground --port 54321 --session-idle 1234ms --daemon-idle 75000ms"; assert!(script.is_ascii()); - assert!( - script - .contains("daemon start --port 54321 --session-idle 1234ms --daemon-idle 75000ms") - ); + assert!(script.contains(detached)); + assert!(script.contains(foreground)); + assert!(script.contains("Detached daemon restart failed; retrying in foreground mode.")); + assert!(script.find(detached) < script.find(foreground)); + assert!(script.find(foreground) < script.find(":restarted")); assert!(!windows_replacement_script(None, 120).contains("daemon start")); } diff --git a/crates/bsk-cli/tests/windows_update.rs b/crates/bsk-cli/tests/windows_update.rs index 1c0d15ee..4bf36560 100644 --- a/crates/bsk-cli/tests/windows_update.rs +++ b/crates/bsk-cli/tests/windows_update.rs @@ -314,9 +314,6 @@ fn automatic_update_exits_old_daemon_and_restarts_on_the_same_port() { .is_some(), "old daemon must exit" ); - fixture.wait_for("helper cleanup", || { - fs::read_dir(fixture.exe.parent().unwrap()).unwrap().count() == 1 - }); let status = fixture .command() .args(["--json", "status"]) @@ -332,6 +329,15 @@ fn automatic_update_exits_old_daemon_and_restarts_on_the_same_port() { 1, "the replacement must not stage another update immediately" ); + let stop = fixture.command().args(["daemon", "stop"]).output().unwrap(); + assert!( + stop.status.success(), + "{}", + String::from_utf8_lossy(&stop.stderr) + ); + fixture.wait_for("replacement daemon stop", || fixture.info().is_none()); + // A foreground fallback intentionally keeps the update helper alive as its + // parent until the daemon stops, so helper-file cleanup is not required here. } fn unused_port() -> u16 {